manage submission - using api fetched listing data
This commit is contained in:
parent
dcc05fc762
commit
eefb85bbde
@ -4,180 +4,9 @@ import AdminHeader from '../../components/admin/AdminHeader';
|
|||||||
import Table from '../../components/common/Table';
|
import Table from '../../components/common/Table';
|
||||||
import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg';
|
import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg';
|
||||||
import DownloadIcon from '../../assets/images/DownloadSimple.svg';
|
import DownloadIcon from '../../assets/images/DownloadSimple.svg';
|
||||||
import ActionIcon from '../../assets/images/qlementine-icons_preview-16.svg';
|
|
||||||
import CaretUp from '../../assets/images/caret-up.svg';
|
import CaretUp from '../../assets/images/caret-up.svg';
|
||||||
import { TextField, SelectField } from '../../components/common/FormControls';
|
import { SelectField } from '../../components/common/FormControls';
|
||||||
|
import { getSubmissions } from '../../services/admin/submission.js';
|
||||||
const submissions = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
establishment: 'CASCADE MARINE FOODS LLC',
|
|
||||||
emirate: 'Sharjah',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q4',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 10,
|
|
||||||
submittedBy: 'Ahmed (Owner)',
|
|
||||||
status: 'Pending',
|
|
||||||
reviewer: 'Omar',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
establishment: 'Khazan meat factory',
|
|
||||||
emirate: 'Sharjah',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q3',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 6,
|
|
||||||
submittedBy: 'Sara (Admin)',
|
|
||||||
status: 'Approved',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
establishment: 'SPINNEYS FRESH FOOD INDUSTRIES L.L.C',
|
|
||||||
emirate: 'Dubai',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q2',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 8,
|
|
||||||
submittedBy: 'Shaik (Admin)',
|
|
||||||
status: 'Approved',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
establishment: 'FRESHLY FROZEN FOODS FACTORY L.L.C',
|
|
||||||
emirate: 'Dubai',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q1',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 4,
|
|
||||||
submittedBy: 'Abdul (Manager)',
|
|
||||||
status: 'Rejected',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
establishment: 'SAHAR FOOD INDUSTRY (L.L.C)',
|
|
||||||
emirate: 'Dubai',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q4',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 8,
|
|
||||||
submittedBy: 'Ahmed (Owner)',
|
|
||||||
status: 'Approved',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
establishment: 'CONTINENTAL FOOD PROCESSING LLC',
|
|
||||||
emirate: 'Ajman',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q3',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 5,
|
|
||||||
submittedBy: 'Sara (Admin)',
|
|
||||||
status: 'Rejected',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
establishment: 'AL KHAZNA POULTRY FARM',
|
|
||||||
emirate: 'Abu Dhabi',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q2',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 7,
|
|
||||||
submittedBy: 'Sara (Admin)',
|
|
||||||
status: 'Approved',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
establishment: 'DIAMOND MEAT PROCESSING L.L.C',
|
|
||||||
emirate: 'Abu Dhabi',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q2',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 7,
|
|
||||||
submittedBy: 'Ahmed (Owner)',
|
|
||||||
status: 'Approved',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 9,
|
|
||||||
establishment: 'ALLIANCE FOODS CO L.L.C',
|
|
||||||
emirate: 'Abu Dhabi',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q2',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 7,
|
|
||||||
submittedBy: 'Ahmed (Owner)',
|
|
||||||
status: 'Approved',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 10,
|
|
||||||
establishment: 'KRUSTASIA FOODS L.L.C',
|
|
||||||
emirate: 'Dubai',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q1',
|
|
||||||
submittedAt: '18/10/2025, 10:40 AM',
|
|
||||||
products: 8,
|
|
||||||
submittedBy: 'Sara (Admin)',
|
|
||||||
status: 'Pending',
|
|
||||||
reviewer: 'Fathima',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11,
|
|
||||||
establishment: 'AL TAMEER FOOD INDUSTRIES',
|
|
||||||
emirate: 'Abu Dhabi',
|
|
||||||
year: '2025',
|
|
||||||
quarter: 'Q3',
|
|
||||||
submittedAt: '19/10/2025, 8:15 AM',
|
|
||||||
products: 11,
|
|
||||||
submittedBy: 'Ahmed (Owner)',
|
|
||||||
status: 'Pending',
|
|
||||||
reviewer: 'Omar',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 12,
|
|
||||||
establishment: 'AL RAWABI DAIRY COMPANY',
|
|
||||||
emirate: 'Dubai',
|
|
||||||
year: '2024',
|
|
||||||
quarter: 'Q4',
|
|
||||||
submittedAt: '12/09/2024, 4:10 PM',
|
|
||||||
products: 9,
|
|
||||||
submittedBy: 'Mariam (Admin)',
|
|
||||||
status: 'Approved',
|
|
||||||
reviewer: 'Abdul',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 13,
|
|
||||||
establishment: 'EMIRATES FRESH POULTRY',
|
|
||||||
emirate: 'Ajman',
|
|
||||||
year: '2024',
|
|
||||||
quarter: 'Q4',
|
|
||||||
submittedAt: '05/09/2024, 11:05 AM',
|
|
||||||
products: 6,
|
|
||||||
submittedBy: 'Haroon (Manager)',
|
|
||||||
status: 'Rejected',
|
|
||||||
reviewer: 'Omar',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 14,
|
|
||||||
establishment: 'GULF FISHERIES LLC',
|
|
||||||
emirate: 'Ras Al Khaimah',
|
|
||||||
year: '2024',
|
|
||||||
quarter: 'Q3',
|
|
||||||
submittedAt: '21/08/2024, 9:15 AM',
|
|
||||||
products: 12,
|
|
||||||
submittedBy: 'Owais (Owner)',
|
|
||||||
status: 'Approved',
|
|
||||||
reviewer: 'Fathima',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const STATUS_VARIANTS = {
|
const STATUS_VARIANTS = {
|
||||||
Approved: { background: '#F3FAF4', text: '#2F663C', border: '#F3FAF4' },
|
Approved: { background: '#F3FAF4', text: '#2F663C', border: '#F3FAF4' },
|
||||||
@ -206,6 +35,10 @@ const Badge = ({ children, status }) => {
|
|||||||
|
|
||||||
const ManageSubmissions = () => {
|
const ManageSubmissions = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [submissions, setSubmissions] = React.useState([]);
|
||||||
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
const [error, setError] = React.useState(null);
|
||||||
|
|
||||||
const [search, setSearch] = React.useState('');
|
const [search, setSearch] = React.useState('');
|
||||||
const [year, setYear] = React.useState('');
|
const [year, setYear] = React.useState('');
|
||||||
const [quarter, setQuarter] = React.useState('');
|
const [quarter, setQuarter] = React.useState('');
|
||||||
@ -214,37 +47,68 @@ const ManageSubmissions = () => {
|
|||||||
const [currentPage, setCurrentPage] = React.useState(1);
|
const [currentPage, setCurrentPage] = React.useState(1);
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
|
|
||||||
const yearOptions = React.useMemo(() => ['All', ...new Set(submissions.map((item) => item.year))], []);
|
// 🔹 Fetch data from backend
|
||||||
const quarterOptions = React.useMemo(() => ['All', ...new Set(submissions.map((item) => item.quarter))], []);
|
React.useEffect(() => {
|
||||||
const emirateOptions = React.useMemo(() => ['All', ...new Set(submissions.map((item) => item.emirate))], []);
|
const fetchSubmissions = async () => {
|
||||||
const statusOptions = React.useMemo(() => ['All', ...new Set(submissions.map((item) => item.status))], []);
|
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
|
||||||
|
}));
|
||||||
|
|
||||||
|
setSubmissions(mapped);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load submissions', err);
|
||||||
|
setError('Unable to load submissions. Please try again later.');
|
||||||
|
} finally {
|
||||||
|
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]);
|
||||||
|
const emirateOptions = React.useMemo(() => ['All', ...new Set(submissions.map((i) => i.emirate))], [submissions]);
|
||||||
|
const statusOptions = React.useMemo(() => ['All', ...new Set(submissions.map((i) => i.status))], [submissions]);
|
||||||
|
|
||||||
const filtered = React.useMemo(() => {
|
const filtered = React.useMemo(() => {
|
||||||
return submissions.filter((item) => {
|
return submissions.filter((item) => {
|
||||||
const matchesSearch = search
|
const matchSearch = search
|
||||||
? item.establishment.toLowerCase().includes(search.toLowerCase()) ||
|
? item.establishment?.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
item.submittedBy.toLowerCase().includes(search.toLowerCase())
|
item.submittedBy?.toLowerCase().includes(search.toLowerCase())
|
||||||
: true;
|
: true;
|
||||||
const matchesYear = !year || year === 'All' || item.year === year;
|
const matchYear = !year || year === 'All' || item.year === year;
|
||||||
const matchesQuarter = !quarter || quarter === 'All' || item.quarter === quarter;
|
const matchQuarter = !quarter || quarter === 'All' || item.quarter === quarter;
|
||||||
const matchesEmirate = !emirate || emirate === 'All' || item.emirate === emirate;
|
const matchEmirate = !emirate || emirate === 'All' || item.emirate === emirate;
|
||||||
const matchesStatus = !status || status === 'All' || item.status === status;
|
const matchStatus = !status || status === 'All' || item.status === status;
|
||||||
return matchesSearch && matchesYear && matchesQuarter && matchesEmirate && matchesStatus;
|
return matchSearch && matchYear && matchQuarter && matchEmirate && matchStatus;
|
||||||
});
|
});
|
||||||
}, [search, year, quarter, emirate, status]);
|
}, [submissions, search, year, quarter, emirate, status]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
const summaryCounts = React.useMemo(() => ({
|
||||||
setCurrentPage(1);
|
total: submissions.length,
|
||||||
}, [search, year, quarter, emirate, status]);
|
approved: submissions.filter((i) => i.status === 'Approved').length,
|
||||||
|
pending: submissions.filter((i) => i.status === 'Pending').length,
|
||||||
const summaryCounts = React.useMemo(() => {
|
rejected: submissions.filter((i) => i.status === 'Rejected').length,
|
||||||
return {
|
}), [submissions]);
|
||||||
total: submissions.length,
|
|
||||||
approved: submissions.filter((item) => item.status === 'Approved').length,
|
|
||||||
pending: submissions.filter((item) => item.status === 'Pending').length,
|
|
||||||
rejected: submissions.filter((item) => item.status === 'Rejected').length,
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
'Establishment',
|
'Establishment',
|
||||||
@ -259,116 +123,61 @@ const ManageSubmissions = () => {
|
|||||||
'Actions',
|
'Actions',
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableRows = React.useMemo(
|
const tableRows = filtered.map((item) => [
|
||||||
() =>
|
item.establishment,
|
||||||
filtered.map((item) => [
|
item.emirate,
|
||||||
item.establishment,
|
item.year,
|
||||||
item.emirate,
|
item.quarter,
|
||||||
item.year,
|
item.products,
|
||||||
item.quarter,
|
item.submittedBy,
|
||||||
item.products,
|
item.submittedAt,
|
||||||
item.submittedBy,
|
item.status,
|
||||||
item.submittedAt,
|
item.reviewer,
|
||||||
item.status,
|
item.id,
|
||||||
item.reviewer,
|
]);
|
||||||
item.id,
|
|
||||||
]),
|
|
||||||
[filtered]
|
|
||||||
);
|
|
||||||
|
|
||||||
const columnWidths = React.useMemo(
|
const columnWidths = [302, 105, 83, 91, 100, 152, 208, 120, 104, 119];
|
||||||
() => [
|
|
||||||
302,
|
if (loading) {
|
||||||
105,
|
return (
|
||||||
83,
|
<div className="flex items-center justify-center h-screen text-[#92722A]">
|
||||||
91,
|
<svg className="animate-spin h-6 w-6 mr-3 text-[#92722A]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
100,
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
152,
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
|
||||||
208,
|
</svg>
|
||||||
120,
|
Loading submissions...
|
||||||
104,
|
</div>
|
||||||
119,
|
);
|
||||||
],
|
}
|
||||||
[]
|
|
||||||
);
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-screen text-red-600">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const toolbar = (
|
const toolbar = (
|
||||||
<div className="px-6 pt-6 pb-5 space-y-4">
|
<div className="px-6 pt-6 pb-5 space-y-4">
|
||||||
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
||||||
<h2 className="text-[18px] leading-[28px] font-medium text-[#232528]">
|
<h2 className="text-[18px] font-medium text-[#232528]">
|
||||||
Manage Submissions <span className="text-[#7A7F87]">({filtered.length})</span>
|
Manage Submissions <span className="text-[#7A7F87]">({filtered.length})</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="w-full md:w-auto">
|
<div className="flex flex-wrap gap-3">
|
||||||
<div className="flex flex-wrap gap-3 md:flex-nowrap">
|
<div className="relative w-[320px]">
|
||||||
<div className="relative" style={{ width: '320px', height: '40px' }}>
|
<img src={SearchIcon} alt="Search" className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4" />
|
||||||
<img src={SearchIcon} alt="Search" className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4" />
|
<input
|
||||||
<input
|
value={search}
|
||||||
type="text"
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
value={search}
|
placeholder="Search Establishment"
|
||||||
onChange={(event) => setSearch(event.target.value)}
|
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]"
|
||||||
placeholder="Search Establishment"
|
/>
|
||||||
className="h-full w-full rounded-md border border-[#C3C6CB] pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full md:w-[134px]" style={{ height: '40px' }}>
|
|
||||||
<SelectField
|
|
||||||
label={null}
|
|
||||||
value={quarter}
|
|
||||||
onChange={(event) => setQuarter(event.target.value)}
|
|
||||||
options={quarterOptions.map((value) => ({ label: value === 'All' ? 'All' : value, value }))}
|
|
||||||
placeholder="Quarter"
|
|
||||||
width="100%"
|
|
||||||
variant="toolbar"
|
|
||||||
allowClear
|
|
||||||
onClear={() => setQuarter('All')}
|
|
||||||
clearValue="All"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full flex-wrap gap-3 md:w-auto md:flex-nowrap">
|
|
||||||
<div className="w-full md:w-[134px]" style={{ height: '40px' }}>
|
|
||||||
<SelectField
|
|
||||||
label={null}
|
|
||||||
value={year}
|
|
||||||
onChange={(event) => setYear(event.target.value)}
|
|
||||||
options={yearOptions.map((value) => ({ label: value === 'All' ? 'All' : value, value }))}
|
|
||||||
placeholder="Year"
|
|
||||||
width="100%"
|
|
||||||
variant="toolbar"
|
|
||||||
allowClear
|
|
||||||
onClear={() => setYear('All')}
|
|
||||||
clearValue="All"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full md:w-[134px]" style={{ height: '40px' }}>
|
|
||||||
<SelectField
|
|
||||||
label={null}
|
|
||||||
value={status}
|
|
||||||
onChange={(event) => setStatus(event.target.value)}
|
|
||||||
options={statusOptions.map((value) => ({ label: value === 'All' ? 'All' : value, value }))}
|
|
||||||
placeholder="Status"
|
|
||||||
width="100%"
|
|
||||||
variant="toolbar"
|
|
||||||
allowClear
|
|
||||||
onClear={() => setStatus('All')}
|
|
||||||
clearValue="All"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="w-full md:w-[134px]" style={{ height: '40px' }}>
|
|
||||||
<SelectField
|
|
||||||
label={null}
|
|
||||||
value={emirate}
|
|
||||||
onChange={(event) => setEmirate(event.target.value)}
|
|
||||||
options={emirateOptions.map((value) => ({ label: value === 'All' ? 'All' : value, value }))}
|
|
||||||
placeholder="Emirates"
|
|
||||||
width="100%"
|
|
||||||
variant="toolbar"
|
|
||||||
allowClear
|
|
||||||
onClear={() => setEmirate('All')}
|
|
||||||
clearValue="All"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<SelectField value={quarter} onChange={(e) => setQuarter(e.target.value)} options={quarterOptions.map((v) => ({ label: v, value: v }))} placeholder="Quarter" width="134px" variant="toolbar" />
|
||||||
|
<SelectField value={year} onChange={(e) => setYear(e.target.value)} options={yearOptions.map((v) => ({ label: v, value: v }))} placeholder="Year" width="134px" variant="toolbar" />
|
||||||
|
<SelectField value={status} onChange={(e) => setStatus(e.target.value)} options={statusOptions.map((v) => ({ label: v, value: v }))} placeholder="Status" width="134px" variant="toolbar" />
|
||||||
|
<SelectField value={emirate} onChange={(e) => setEmirate(e.target.value)} options={emirateOptions.map((v) => ({ label: v, value: v }))} placeholder="Emirates" width="134px" variant="toolbar" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -380,53 +189,37 @@ const ManageSubmissions = () => {
|
|||||||
<div className="max-w-[1280px] mx-auto px-4 py-6 flex flex-col gap-6">
|
<div className="max-w-[1280px] mx-auto px-4 py-6 flex flex-col gap-6">
|
||||||
<nav className="flex items-center gap-2 text-sm text-[#8F9299]">
|
<nav className="flex items-center gap-2 text-sm text-[#8F9299]">
|
||||||
<Link to="/admin/dashboard" className="flex items-center gap-2 text-[#232528] hover:underline">
|
<Link to="/admin/dashboard" className="flex items-center gap-2 text-[#232528] hover:underline">
|
||||||
Dashboard
|
Dashboard <img src={CaretUp} alt="Dashboard" className="h-3 w-3" />
|
||||||
<img src={CaretUp} alt="Dashboard" className="h-3 w-3" />
|
|
||||||
</Link>
|
</Link>
|
||||||
<span className="flex items-center gap-2 text-[#92722A] font-medium">
|
<span className="flex items-center gap-2 text-[#92722A] font-medium">Manage Submissions</span>
|
||||||
Manage Submissions
|
|
||||||
{/* <img src={CaretUp} alt="Manage Submissions" className="h-3 w-3" /> */}
|
|
||||||
</span>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 rounded-[12px] bg-white px-5 py-3 shadow-[0_12px_24px_rgba(15,23,42,0.06)] md:flex-row md:items-center md:justify-between">
|
{/* Summary bar */}
|
||||||
<div className="flex flex-wrap gap-[6px] ">
|
<div className="flex flex-wrap gap-[6px] rounded-[12px] bg-white px-5 py-3 shadow">
|
||||||
{(
|
{Object.entries(summaryCounts).map(([key, val]) => {
|
||||||
[
|
const v = SUMMARY_VARIANTS[key];
|
||||||
{ key: 'total', label: 'Total', value: summaryCounts.total },
|
|
||||||
{ key: 'approved', label: 'Approved', value: summaryCounts.approved },
|
|
||||||
{ key: 'pending', label: 'Pending', value: summaryCounts.pending },
|
|
||||||
{ key: 'rejected', label: 'Rejected', value: summaryCounts.rejected },
|
|
||||||
]
|
|
||||||
).map((item) => {
|
|
||||||
const variant = SUMMARY_VARIANTS[item.key];
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={key} className="inline-flex items-center gap-[6px] rounded-[8px] px-2 py-[2px]" style={{ background: v.background }}>
|
||||||
key={item.key}
|
<span className="font-medium text-[13px]" style={{ color: v.label }}>
|
||||||
className="inline-flex h-[28px] items-center justify-start gap-[6px] rounded-[8px] px-2 py-[2px] text-[13px] ml-5"
|
{key.charAt(0).toUpperCase() + key.slice(1)}:
|
||||||
style={{ background: variant.background }}
|
|
||||||
>
|
|
||||||
<span className="font-medium" style={{ color: variant.label }}>
|
|
||||||
{item.label}:
|
|
||||||
</span>
|
</span>
|
||||||
<span className="font-semibold" style={{ color: variant.value }}>
|
<span className="font-semibold text-[13px]" style={{ color: v.value }}>
|
||||||
{item.value}
|
{val}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex h-10 w-full items-center justify-center gap-3 rounded-[6px] border border-[#C3C6CB] bg-white px-5 text-sm font-medium text-[#5F646D] transition-colors hover:bg-[#F7F7F7] md:w-[143px]"
|
className="ml-auto inline-flex h-10 items-center justify-center gap-3 rounded-[6px] border border-[#C3C6CB] bg-white px-5 text-sm font-medium text-[#5F646D] hover:bg-[#F7F7F7]"
|
||||||
style={{ paddingTop: '10px', paddingBottom: '10px' }}
|
|
||||||
>
|
>
|
||||||
<img src={DownloadIcon} alt="Export" className="h-4 w-4" />
|
<img src={DownloadIcon} alt="Export" className="h-4 w-4" />
|
||||||
<span>Export CSV</span>
|
Export CSV
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-2xl border border-[#E5E7EB] bg-white shadow-[0_16px_32px_rgba(15,23,42,0.06)] overflow-hidden">
|
{/* Table */}
|
||||||
|
<div className="rounded-2xl border border-[#E5E7EB] bg-white shadow overflow-hidden">
|
||||||
<Table
|
<Table
|
||||||
headers={headers}
|
headers={headers}
|
||||||
rows={tableRows}
|
rows={tableRows}
|
||||||
@ -440,36 +233,17 @@ const ManageSubmissions = () => {
|
|||||||
totalItems: filtered.length,
|
totalItems: filtered.length,
|
||||||
}}
|
}}
|
||||||
renderCell={(value, rowIndex, columnIndex) => {
|
renderCell={(value, rowIndex, columnIndex) => {
|
||||||
if (columnIndex === 7) {
|
if (columnIndex === 7)
|
||||||
|
return <div className="flex justify-start"><Badge status={String(value)}>{value}</Badge></div>;
|
||||||
|
if (columnIndex === 9)
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-start">
|
<button
|
||||||
<Badge status={String(value)}>{value}</Badge>
|
onClick={() => navigate(`/admin/validations/${value}`)}
|
||||||
</div>
|
className="text-sm font-medium text-[#92722A] hover:underline"
|
||||||
|
>
|
||||||
|
View Details
|
||||||
|
</button>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (columnIndex === 9) {
|
|
||||||
return (
|
|
||||||
<div className="flex h-12 w-full items-center justify-start">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="whitespace-nowrap text-sm font-medium text-[#92722A]"
|
|
||||||
onClick={() => navigate(`/admin/validations/${value}`)}
|
|
||||||
>
|
|
||||||
View Details
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (columnIndex === 5) {
|
|
||||||
return <span className="text-sm text-[#232528]">{value}</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (columnIndex === 8) {
|
|
||||||
return <span className="text-sm text-[#232528]">{value}</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
18
ipi-survey-platform/src/services/admin/submission.js
Normal file
18
ipi-survey-platform/src/services/admin/submission.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import apiClient from '../api/apiClient.js';
|
||||||
|
|
||||||
|
/** Fetch all submissions for the listing page */
|
||||||
|
export const getSubmissions = async () => {
|
||||||
|
const response = await apiClient.get('/submissions');
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 🔹 Fetch a single submission by ID */
|
||||||
|
export const getSubmissionById = async (id) => {
|
||||||
|
const response = await apiClient.get(`/submissions/${id}`);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getSubmissions,
|
||||||
|
getSubmissionById,
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user