API integration done for admin dashboard submission listing

This commit is contained in:
Senthamilselvi 2025-10-29 16:13:33 +05:30
parent ca887ceb80
commit f231275add
3 changed files with 116 additions and 139 deletions

View File

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"axios": "^1.12.2", "axios": "^1.12.2",
"lucide-react": "^0.548.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-google-recaptcha": "^3.1.0", "react-google-recaptcha": "^3.1.0",
@ -3190,6 +3191,15 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lucide-react": {
"version": "0.548.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.548.0.tgz",
"integrity": "sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.19", "version": "0.30.19",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",

View File

@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.12.2", "axios": "^1.12.2",
"lucide-react": "^0.548.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-google-recaptcha": "^3.1.0", "react-google-recaptcha": "^3.1.0",

View File

@ -1,159 +1,115 @@
import React from 'react'; import React, { useEffect, useState, useMemo } from 'react';
import Table from '../common/Table'; import Table from '../common/Table';
import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg'; import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg';
import { CheckCircle, XCircle, Eye } from 'lucide-react'; // Icons
const submissions = [ import apiClient from '../../services/api/apiClient.js';
{
establishment: 'CASCADE MARINE FOODS LLC',
emirate: 'Sharjah',
productCount: '5',
year: '2025',
quarter: 'Q4',
lastSubmission: '10/12/2024',
deadline: '20/12/2024',
},
{
establishment: 'Khazan meat factory',
emirate: 'Sharjah',
productCount: '8',
year: '2025',
quarter: 'Q3',
lastSubmission: '15/11/2024',
deadline: '15/12/2024',
},
{
establishment: 'SPINNEYS FRESH FOOD INDUSTRIES L.L.C',
emirate: 'Dubai',
productCount: '6',
year: '2025',
quarter: 'Q2',
lastSubmission: '12/12/2024',
deadline: '10/12/2024',
},
{
establishment: 'FRESHLY FROZEN FOODS FACTORY L.L.C',
emirate: 'Dubai',
productCount: '8',
year: '2025',
quarter: 'Q1',
lastSubmission: '08/12/2024',
deadline: '18/12/2024',
},
{
establishment: 'SAHAR FOOD INDUSTRY (L.L.C)',
emirate: 'Dubai',
productCount: '8',
year: '2025',
quarter: 'Q4',
lastSubmission: '08/12/2024',
deadline: '18/12/2024',
},
{
establishment: 'CONTINENTAL FOOD PROCESSING LLC',
emirate: 'Ajman',
productCount: '8',
year: '2025',
quarter: 'Q3',
lastSubmission: '08/12/2024',
deadline: '18/12/2024',
},
{
establishment: 'AL KHAZNA POULTRY FARM',
emirate: 'Abu Dhabi',
productCount: '8',
year: '2025',
quarter: 'Q2',
lastSubmission: '08/12/2024',
deadline: '18/12/2024',
},
{
establishment: 'DIAMOND MEAT PROCESSING L.L.C',
emirate: 'Abu Dhabi',
productCount: '8',
year: '2025',
quarter: 'Q2',
lastSubmission: '08/12/2024',
deadline: '18/12/2024',
},
{
establishment: 'ALLIANCE FOODS CO L.L.C',
emirate: 'Abu Dhabi',
productCount: '8',
year: '2025',
quarter: 'Q2',
lastSubmission: '08/12/2024',
deadline: '18/12/2024',
},
{
establishment: 'KRUSTASIA FOODS L.L.C',
emirate: 'Dubai',
productCount: '4',
year: '2025',
quarter: 'Q1',
lastSubmission: '14/12/2024',
deadline: '21/12/2024',
},
];
const SubmissionTable = () => { const SubmissionTable = () => {
const [search, setSearch] = React.useState(''); const [search, setSearch] = useState('');
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
const filtered = React.useMemo(() => { useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await apiClient.get('/admin_dashboard');
const recent = response?.data?.recent_submissions || [];
const mappedData = recent.map((item) => ({
establishment: item.establishment || '-',
emirate: item.emirate || '-',
year: item.year || '-',
quarter: item.quarter || '-',
productCount: item.product_count ?? '-',
submittedBy: item.created_by || '-',
submissionDateTime: formatDate(item.created_at),
status: item.status || '-',
reviewer: item.updated_by || '-',
reviewedOn: formatDate(item.updated_at),
}));
setSubmissions(mappedData);
} catch (error) {
console.error('Error loading submissions:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
const formatDate = (dateStr) => {
if (!dateStr) return '-';
const date = new Date(dateStr);
return `${date.toLocaleDateString()} ${date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})}`;
};
const filtered = useMemo(() => {
if (!search.trim()) return submissions; if (!search.trim()) return submissions;
const normalized = search.trim().toLowerCase(); const normalized = search.trim().toLowerCase();
return submissions.filter((item) => return submissions.filter((item) =>
[ Object.values(item).join(' ').toLowerCase().includes(normalized)
item.establishment,
item.emirate,
item.productCount,
item.year,
item.quarter,
item.lastSubmission,
item.deadline,
]
.join(' ')
.toLowerCase()
.includes(normalized)
); );
}, [search]); }, [search, submissions]);
const headers = [
'Establishment',
'Emirates',
'Year',
'Quarter',
'Product Count',
'Submitted By',
'Submission Date & Time',
'Status',
'Reviewer',
'Reviewer On',
'Actions',
];
const headers = ['Establishment', 'Emirates', 'Product Count', 'Year', 'Quarter', 'Last Submission', 'Deadline'];
const tableRows = filtered.map((item) => [ const tableRows = filtered.map((item) => [
item.establishment, item.establishment,
item.emirate, item.emirate,
item.productCount,
item.year, item.year,
item.quarter, item.quarter,
item.lastSubmission, item.productCount,
item.deadline, item.submittedBy,
item.submissionDateTime,
item.status,
item.reviewer,
item.reviewedOn,
// 🧩 Actions column
<div className="flex items-center justify-center gap-3">
<CheckCircle className="text-green-600 w-5 h-5 cursor-pointer hover:scale-110 transition-transform" />
<XCircle className="text-red-500 w-5 h-5 cursor-pointer hover:scale-110 transition-transform" />
<Eye className="text-gray-500 w-5 h-5 cursor-pointer hover:scale-110 transition-transform" />
</div>,
]); ]);
const columnWidths = React.useMemo( const columnWidths = [240, 120, 100, 100, 130, 150, 180, 120, 150, 180, 120];
() => [
317,
161,
140,
119,
98,
158,
150,
],
[]
);
const toolbar = ( const toolbar = (
<div className="px-6 pt-6 pb-4"> <div className="px-6 pt-6 pb-4">
<div className="mx-auto flex h-10 w-full max-w-[1280px] items-center justify-between gap-4"> <div className="mx-auto flex h-10 w-full max-w-[1280px] items-center justify-between gap-4">
<h2 className="text-[18px] leading-[28px] font-medium text-[#232528]"> <h2 className="text-[18px] leading-[28px] font-medium text-[#232528]">
Recent Submissions <span className="text-[#7A7F87]">({submissions.length})</span> Recent Submissions{' '}
<span className="text-[#7A7F87]">({submissions.length})</span>
</h2> </h2>
<div className="relative w-[280px]"> <div className="relative w-[280px]">
<img src={SearchIcon} alt="Search" className="pointer-events-none 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
type="text" type="text"
value={search} value={search}
onChange={(event) => setSearch(event.target.value)} onChange={(event) => setSearch(event.target.value)}
placeholder="Search Establishment" placeholder="Search Establishment or Status"
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]" 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]"
/> />
</div> </div>
@ -162,17 +118,27 @@ const SubmissionTable = () => {
); );
return ( return (
<div className="border border-[#E5E7EB] bg-white shadow-[0_16px_32px_rgba(15,23,42,0.06)]" style={{ width: '1250px', height: '706px', borderRadius: '8px' }}> <div
<div className="space-y-[1px]"> className="border border-[#E5E7EB] bg-white shadow-[0_16px_32px_rgba(15,23,42,0.06)]"
style={{ width: '1250px', height: '706px', borderRadius: '8px' }}
>
<div className="space-y-[1px] overflow-x-auto"> {/* ✅ Horizontal scroll */}
{toolbar} {toolbar}
<Table {loading ? (
headers={headers} <div className="flex items-center justify-center h-[500px] text-gray-500">
rows={tableRows} Loading submissions...
separated </div>
rowGapClass="border-spacing-y-1" ) : (
columnWidths={columnWidths} <div className="min-w-[1200px]"> {/* Keeps layout inside scroll area */}
disableHorizontalScroll <Table
/> headers={headers}
rows={tableRows}
separated
rowGapClass="border-spacing-y-1"
columnWidths={columnWidths}
/>
</div>
)}
</div> </div>
<div className="flex justify-end px-6 pb-6"> <div className="flex justify-end px-6 pb-6">
<button <button
@ -186,4 +152,4 @@ const SubmissionTable = () => {
); );
}; };
export default SubmissionTable; export default SubmissionTable;