import { useState, useEffect, useMemo } from 'react'; import { Grid, Card, Box, Stack, Typography, Button, Divider, LinearProgress, CircularProgress, Alert, Table, TableBody, TableCell, TableHead, TableRow, TableContainer } from '@mui/material'; import RefreshIcon from '@mui/icons-material/Refresh'; import ApartmentOutlinedIcon from '@mui/icons-material/ApartmentOutlined'; import FiberNewOutlinedIcon from '@mui/icons-material/FiberNewOutlined'; import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; import HandshakeOutlinedIcon from '@mui/icons-material/HandshakeOutlined'; import HistoryOutlinedIcon from '@mui/icons-material/HistoryOutlined'; import DonutLargeOutlinedIcon from '@mui/icons-material/DonutLargeOutlined'; import CategoryOutlinedIcon from '@mui/icons-material/CategoryOutlined'; import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined'; import PageHeader from '@/components/PageHeader'; import StatCard from '@/components/StatCard'; import StatusChip from '@/components/StatusChip'; import DonutChart from '@/components/charts/DonutChart'; import UserAvatar from '@/components/UserAvatar'; import EmptyState from '@/components/EmptyState'; import { fetchClients, fetchUsers } from '@/utils/apiClient'; import { toClient, toUser } from '@/utils/mappers'; import { titleCase } from '@/utils/format'; const STATUS_COLOR = { newclient: '#00A2AE', contacted: '#FFBF00', onboarded: '#00A854', lost: '#F04134' }; const statusColor = (s) => STATUS_COLOR[String(s || '').toLowerCase()] || '#8C8C8C'; const BAR_COLORS = ['#C01227', '#00A2AE', '#00A854', '#FFBF00', '#9E0E20', '#8C8C8C', '#D6515C']; function Panel({ icon: Icon, title, action, color = 'primary', noPadding = false, children }) { return ( `linear-gradient(90deg, ${theme.palette[color].lighter}66 0%, transparent 100%)` }} > {title} {action} {children} ); } export default function Dashboard() { const [clients, setClients] = useState([]); const [team, setTeam] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const load = () => { setLoading(true); setError(null); Promise.all([fetchClients(), fetchUsers()]) .then(([cs, us]) => { setClients((cs || []).map(toClient)); setTeam((us || []).map(toUser)); }) .catch((e) => setError(e.message || 'Failed to load dashboard data')) .finally(() => setLoading(false)); }; useEffect(() => { load(); }, []); const stats = useMemo(() => ({ total: clients.length, newCount: clients.filter((c) => c.status === 'newClient').length, parcels: clients.reduce((s, c) => s + c.parcelVolume, 0), contracts: clients.reduce((s, c) => s + c.activeContracts, 0) }), [clients]); const statusData = useMemo(() => { const m = {}; clients.forEach((c) => { m[c.status] = (m[c.status] || 0) + 1; }); return Object.entries(m).sort((a, b) => b[1] - a[1]).map(([status, value]) => ({ label: titleCase(status), value, color: statusColor(status) })); }, [clients]); const byType = useMemo(() => { const m = {}; clients.forEach((c) => { const t = c.businessType || 'other'; m[t] = (m[t] || 0) + 1; }); return Object.entries(m).sort((a, b) => b[1] - a[1]); }, [clients]); const maxType = byType[0]?.[1] || 1; const recent = useMemo( () => [...clients].sort((a, b) => String(b.lastUpdated).localeCompare(String(a.lastUpdated))).slice(0, 6), [clients] ); const today = new Date().toLocaleDateString('en-IN', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); if (loading) { return ( <> ); } return ( <> } onClick={load}>Refresh} /> {error && Retry}>{error}} {recent.length === 0 ? ( ) : ( Client Type Location Parcels Status {recent.map((c) => ( {c.name} {titleCase(c.businessType) || '—'} {c.city || '—'}{c.businessState ? `, ${c.businessState}` : ''} {c.parcelVolume.toLocaleString('en-IN')} ))}
)}
{statusData.length === 0 ? : } {byType.length === 0 ? ( ) : ( {byType.map(([type, count], i) => ( {titleCase(type)} {count} · {Math.round((count / clients.length) * 100)}% ))} )} {team.length === 0 ? ( ) : ( } spacing={0}> {team.slice(0, 6).map((u) => ( {u.name} {u.email} ))} )}
); }