Admin Dashboard updated design & API integration done

This commit is contained in:
Senthamilselvi 2025-10-29 16:42:35 +05:30
parent f231275add
commit 31baf04159
3 changed files with 199 additions and 32 deletions

View File

@ -5,14 +5,21 @@ const StatCard = ({ icon = null, label, value, width = 200.4 }) => {
return (
<div
className="flex h-14 items-center rounded-[8px] border border-[#E3E5E8] bg-white px-4 shadow-[0_8px_20px_rgba(42,47,66,0.06)]"
style={{ width: resolvedWidth, columnGap: '4px' }}
className="flex flex-col items-center justify-center rounded-[8px] border border-[#E3E5E8] bg-white px-5 py-5 shadow-[0_8px_20px_rgba(42,47,66,0.06)]"
style={{ width: resolvedWidth, minHeight: '120px' }}
>
<span className="inline-flex h-6 w-6 items-center justify-start shrink-0">{icon}</span>
<span className="truncate text-[16px] leading-[24px] font-medium text-[#5F646D] ml-2">{label}</span>
<span className="text-[16px] leading-[24px] font-semibold text-[#232528] ml-2">{value}</span>
<div className="flex items-center justify-center gap-2">
{icon && <div className="flex items-center justify-center w-5 h-5">{icon}</div>}
<span className="text-[15px] font-medium text-[#5F646D] leading-[22px] text-center">
{label}
</span>
</div>
<span className="mt-2 text-[24px] font-semibold text-[#232528] leading-[30px] text-center">
{value}
</span>
</div>
);
};
export default StatCard;
export default StatCard;

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState, useMemo } from 'react';
import Table from '../common/Table';
import SearchIcon from '../../assets/images/material-symbols_search-rounded.svg';
import { CheckCircle, XCircle, Eye } from 'lucide-react'; // Icons
import { CheckCircle, XCircle, Eye } from 'lucide-react';
import apiClient from '../../services/api/apiClient.js';
const SubmissionTable = () => {
const SubmissionTable = ({ selectedQuarter, selectedYear }) => {
const [search, setSearch] = useState('');
const [submissions, setSubmissions] = useState([]);
const [loading, setLoading] = useState(true);
@ -49,13 +49,23 @@ const SubmissionTable = () => {
})}`;
};
const filteredByQuarterYear = useMemo(() => {
return submissions.filter((item) => {
const quarterMatch =
selectedQuarter === 'None' || item.quarter === selectedQuarter;
const yearMatch =
selectedYear === 'None' || `${item.year}` === selectedYear;
return quarterMatch && yearMatch;
});
}, [submissions, selectedQuarter, selectedYear]);
const filtered = useMemo(() => {
if (!search.trim()) return submissions;
if (!search.trim()) return filteredByQuarterYear;
const normalized = search.trim().toLowerCase();
return submissions.filter((item) =>
return filteredByQuarterYear.filter((item) =>
Object.values(item).join(' ').toLowerCase().includes(normalized)
);
}, [search, submissions]);
}, [search, filteredByQuarterYear]);
const headers = [
'Establishment',
@ -71,6 +81,35 @@ const SubmissionTable = () => {
'Actions',
];
const renderStatusBadge = (status) => {
const normalized = status?.toLowerCase();
if (normalized === 'approved') {
return (
<span className="px-3 py-1 rounded-md text-green-700 bg-green-50 font-medium text-sm">
Approved
</span>
);
} else if (normalized === 'rejected') {
return (
<span className="px-3 py-1 rounded-md text-red-600 bg-red-50 font-medium text-sm">
Rejected
</span>
);
} else if (normalized === 'pending') {
return (
<span className="px-3 py-1 rounded-md text-blue-600 bg-blue-50 font-medium text-sm">
Pending
</span>
);
} else {
return (
<span className="px-3 py-1 rounded-md text-gray-600 bg-gray-50 font-medium text-sm">
{status || '-'}
</span>
);
}
};
const tableRows = filtered.map((item) => [
item.establishment,
item.emirate,
@ -79,10 +118,9 @@ const SubmissionTable = () => {
item.productCount,
item.submittedBy,
item.submissionDateTime,
item.status,
renderStatusBadge(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" />
@ -90,14 +128,26 @@ const SubmissionTable = () => {
</div>,
]);
const columnWidths = [240, 120, 100, 100, 130, 150, 180, 120, 150, 180, 120];
const columnWidths = [240, 120, 100, 100, 130, 150, 180, 150, 150, 180, 120];
const titleText = useMemo(() => {
const count = filtered.length;
if (selectedQuarter !== 'None' && selectedYear !== 'None') {
return `Recent 10 Submissions for Selected Quarter ${selectedQuarter} ${selectedYear} (${count})`;
} else if (selectedQuarter !== 'None') {
return `Recent 10 Submissions for Selected Quarter ${selectedQuarter} (${count})`;
} else if (selectedYear !== 'None') {
return `Recent 10 Submissions for Selected Year ${selectedYear} (${count})`;
} else {
return `Recent 10 Submissions (${count})`;
}
}, [selectedQuarter, selectedYear, filtered]);
const toolbar = (
<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">
<h2 className="text-[18px] leading-[28px] font-medium text-[#232528]">
Recent Submissions{' '}
<span className="text-[#7A7F87]">({submissions.length})</span>
{titleText}
</h2>
<div className="relative w-[280px]">
<img
@ -119,17 +169,17 @@ const SubmissionTable = () => {
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' }}
className="border border-[#E5E7EB] bg-white shadow-[0_16px_32px_rgba(15,23,42,0.06)] rounded-[8px]"
style={{ width: '1250px', height: '706px' }}
>
<div className="space-y-[1px] overflow-x-auto"> {/* ✅ Horizontal scroll */}
<div className="space-y-[1px] overflow-x-auto">
{toolbar}
{loading ? (
<div className="flex items-center justify-center h-[500px] text-gray-500">
Loading submissions...
</div>
) : (
<div className="min-w-[1200px]"> {/* Keeps layout inside scroll area */}
<div className="min-w-[1200px]">
<Table
headers={headers}
rows={tableRows}
@ -140,6 +190,7 @@ const SubmissionTable = () => {
</div>
)}
</div>
<div className="flex justify-end px-6 pb-6">
<button
type="button"
@ -152,4 +203,4 @@ const SubmissionTable = () => {
);
};
export default SubmissionTable;
export default SubmissionTable

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import AdminHeader from '../../components/admin/AdminHeader';
import StatCard from '../../components/admin/StatCard';
import WpfSurveyIcon from '../../assets/images/wpf_survey.svg';
@ -8,27 +8,136 @@ import RejectedIcon from '../../assets/images/warning-circle-fill 1.svg';
import OverdueIcon from '../../assets/images/warning-fill 1.svg';
import PendingIcon from '../../assets/images/info-fill 1.svg';
import SubmissionTable from '../../components/admin/SubmissionTable';
import apiClient from '../../services/api/apiClient.js';
const AdminDashboard = () => {
const [summary, setSummary] = useState({
total_establishments: 0,
submitted: 0,
approved: 0,
rejected: 0,
not_started: 0,
pending: 0,
});
const [loading, setLoading] = useState(true);
const [selectedQuarter, setSelectedQuarter] = useState('None');
const [selectedYear, setSelectedYear] = useState('None');
useEffect(() => {
const fetchDashboardData = async () => {
try {
setLoading(true);
const response = await apiClient.get('/admin_dashboard');
const data = response?.data?.summary || {};
const notStartedCombined = (data.not_started || 0) + (data.overdue || 0);
setSummary({
total_establishments: data.total_establishments || 0,
submitted: data.submitted || 0,
approved: data.approved || 0,
rejected: data.rejected || 0,
not_started: notStartedCombined,
pending: data.pending || 0,
});
} catch (error) {
console.error('Error fetching dashboard summary:', error);
} finally {
setLoading(false);
}
};
fetchDashboardData();
}, []);
return (
<div className="min-h-screen bg-[#F6F5F1]">
<AdminHeader />
<div className="max-w-[1280px] mx-auto box-border px-4 py-8 flex flex-col gap-6">
<div className="w-full overflow-x-auto">
<div className="mx-auto flex h-14 w-full max-w-[1280px] items-center gap-2">
<StatCard label="Total Establishments" value="2" width={290} icon={<img src={WpfSurveyIcon} alt="Total Establishments" className="h-5 w-5" />} />
<StatCard label="Submitted" value="3" width={190} icon={<img src={SubmittedIcon} alt="Submitted" className="h-5 w-5" />} iconBg="#E5F0FF" iconBorder="#D0E2FF" />
<StatCard label="Approved" value="2" width={190} icon={<img src={ApprovedIcon} alt="Approved" className="h-5 w-5" />} iconBg="#E6F4EA" iconBorder="#CBE9D5" />
<StatCard label="Rejected" value="2" width={190} icon={<img src={RejectedIcon} alt="Rejected" className="h-5 w-5" />} iconBg="#FEEDEA" iconBorder="#FFD9D2" />
<StatCard label="Overdue" value="2" width={190} icon={<img src={OverdueIcon} alt="Overdue" className="h-5 w-5" />} iconBg="#FFF3E6" iconBorder="#FFE3C9" />
<StatCard label="Pending" value="2" width={190} icon={<img src={PendingIcon} alt="Pending" className="h-5 w-5" />} iconBg="#FFF7E6" iconBorder="#FFE9B8" />
<div className="flex items-center justify-between w-full max-w-[1280px] mx-auto mt-6 px-4">
<h1 className="text-[18px] font-semibold text-[#232528]">
Admin Dashboard{' '}
<span className="font-normal text-[#5F646D]"> Survey Overview</span>
</h1>
<div className="flex items-center gap-3">
<span className="text-[14px] text-[#232528] font-medium">
Survey Quarter
</span>
<select
value={selectedQuarter}
onChange={(e) => setSelectedQuarter(e.target.value)}
className="border border-[#D0D5DD] rounded-md h-8 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
>
<option>None</option>
<option>Q1</option>
<option>Q2</option>
<option>Q3</option>
<option>Q4</option>
</select>
<select
value={selectedYear}
onChange={(e) => setSelectedYear(e.target.value)}
className="border border-[#D0D5DD] rounded-md h-8 px-2 text-sm focus:outline-none focus:ring-1 focus:ring-[#92722A]"
>
<option>None</option>
<option>2025</option>
<option>2024</option>
<option>2023</option>
<option>2022</option>
</select>
</div>
</div>
<SubmissionTable />
<div className="w-full">
<div className="mx-auto flex w-full max-w-[1280px] items-center gap-4">
<StatCard
label="Total Establishments"
value={loading ? '--' : summary.total_establishments}
width={290}
icon={<img src={WpfSurveyIcon} alt="Total Establishments" className="h-5 w-5" />}
/>
<StatCard
label="Submitted"
value={loading ? '--' : summary.submitted}
width={190}
icon={<img src={SubmittedIcon} alt="Submitted" className="h-5 w-5" />}
/>
<StatCard
label="Approved"
value={loading ? '--' : summary.approved}
width={190}
icon={<img src={ApprovedIcon} alt="Approved" className="h-5 w-5" />}
/>
<StatCard
label="Rejected"
value={loading ? '--' : summary.rejected}
width={190}
icon={<img src={RejectedIcon} alt="Rejected" className="h-5 w-5" />}
/>
<StatCard
label="Not Started"
value={loading ? '--' : summary.not_started}
width={190}
icon={<img src={OverdueIcon} alt="Not Started" className="h-5 w-5" />}
/>
<StatCard
label="Pending"
value={loading ? '--' : summary.pending}
width={190}
icon={<img src={PendingIcon} alt="Pending" className="h-5 w-5" />}
/>
</div>
</div>
<SubmissionTable
selectedQuarter={selectedQuarter}
selectedYear={selectedYear}
/>
</div>
</div>
);
};
export default AdminDashboard;
export default AdminDashboard;