import { useMemo, useState, Fragment } from 'react';
import { useNavigate } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import {
Box,
Typography,
Card,
CardContent,
Grid,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TablePagination,
Chip,
TextField,
InputAdornment,
MenuItem,
Button,
Skeleton,
Tabs,
Tab,
Collapse,
IconButton,
} from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import PendingActionsIcon from '@mui/icons-material/PendingActions';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import TimerIcon from '@mui/icons-material/Timer';
import DescriptionIcon from '@mui/icons-material/Description';
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
import ReportIcon from '@mui/icons-material/Report';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
import { usePaginatedList } from '../../../hooks/usePaginatedList';
import { proposalsApi, type ProposalListItem } from '../../../lib/api/proposals';
import { adminApi, type DashboardStats } from '../../../lib/api/admin';
import { QUERY_KEYS } from '../../../constants/queryKeys';
import { SERVICE_CATEGORIES, PRIORITIES, PROPOSAL_STATUSES, STATUS_COLORS, STATUS_LABELS, PRIORITY_LABELS, PRIORITY_COLORS, PRIORITY_ROW_SX } from '../../../constants';
import { formatBidAmount, formatDate, formatAge, getAgeColor, groupRevisions } from '../../../lib/format';
import { useDocumentTitle } from '../../../hooks/useDocumentTitle';
const COL_COUNT = 11;
const TAB_STATUSES = ['', 'InReview', 'Approved', 'Sent', 'Revised'] as const;
function StatCard({ icon, label, value, color }: { icon: React.ReactNode; label: string; value: string; color: string }) {
return (
{icon}
{label}
{value}
);
}
export default function AdminDashboard({ defaultStatus }: { defaultStatus?: string }) {
useDocumentTitle(defaultStatus ? 'Admin Queue' : 'Admin Dashboard');
const navigate = useNavigate();
const showTabs = !defaultStatus;
const { data: stats, isLoading: statsLoading } = useQuery({
queryKey: [QUERY_KEYS.dashboard],
queryFn: adminApi.getDashboard,
});
const [statusFilter, setStatusFilter] = useState(defaultStatus ?? '');
const [categoryFilter, setCategoryFilter] = useState('');
const [priorityFilter, setPriorityFilter] = useState('');
const [expanded, setExpanded] = useState>(new Set());
const {
rows,
search,
setSearch,
page,
setPage,
pageSize,
setPageSize,
totalCount,
loading,
err,
} = usePaginatedList(proposalsApi.getAll, {
...(statusFilter && { status: statusFilter }),
...(categoryFilter && { serviceCategory: categoryFilter }),
...(priorityFilter && { priority: priorityFilter }),
});
const grouped = useMemo(() => groupRevisions(rows), [rows]);
const toggleExpand = (baseNumber: string) => {
setExpanded((prev) => {
const next = new Set(prev);
if (next.has(baseNumber)) next.delete(baseNumber);
else next.add(baseNumber);
return next;
});
};
const tabIndex = TAB_STATUSES.indexOf(statusFilter as typeof TAB_STATUSES[number]);
const handleTabChange = (_: React.SyntheticEvent, newValue: number) => {
setStatusFilter(TAB_STATUSES[newValue] ?? '');
setPage(1);
};
const hasActiveFilters = statusFilter || categoryFilter || priorityFilter || search;
const clearFilters = () => {
setStatusFilter(defaultStatus ?? '');
setCategoryFilter('');
setPriorityFilter('');
setSearch('');
};
const renderRow = (row: ProposalListItem, indent: boolean, showExpandIcon: boolean, isExpanded: boolean, baseNumber: string) => (
navigate(`/admin/proposals/${row.id}`)}
>
{showExpandIcon && (
{
e.stopPropagation();
toggleExpand(baseNumber);
}}
>
{isExpanded ? : }
)}
{row.proposalNumber}
{row.customerName}
{row.workOrderNumber}
{row.serviceCategory}
: row.priority === 'Urgent' ? : undefined}
/>
{formatBidAmount(row.totalBidAmount)}
{formatDate(row.submittedAt)}
{row.submittedByName || '-'}
{formatAge(row.submittedAt)}
);
return (
Admin Dashboard
{statsLoading ? (
) : (
}
label="Pending Review"
value={String(stats?.pendingCount ?? 0)}
color="#1565c0"
/>
)}
{statsLoading ? (
) : (
}
label="Approved This Week"
value={String(stats?.approvedThisWeek ?? 0)}
color="#2e7d32"
/>
)}
{statsLoading ? (
) : (
}
label="Avg Turnaround"
value={stats ? `${Math.round(stats.avgTurnaroundHours)}h` : '-'}
color="#f57c00"
/>
)}
{statsLoading ? (
) : (
}
label="Total Proposals"
value={String(stats?.totalProposals ?? 0)}
color="#5e35b1"
/>
)}
{showTabs && (
= 0 ? tabIndex : 0}
onChange={handleTabChange}
sx={{ mb: 2, borderBottom: 1, borderColor: 'divider' }}
>
{TAB_STATUSES.slice(1).map((s) => (
))}
)}
setSearch(e.target.value)}
sx={{ width: 240 }}
slotProps={{
input: {
startAdornment: (
),
},
}}
/>
setCategoryFilter(e.target.value)}
>
{SERVICE_CATEGORIES.map((c) => (
))}
setPriorityFilter(e.target.value)}
>
{PRIORITIES.map((p) => (
))}
{!showTabs && (
setStatusFilter(e.target.value)}
>
{PROPOSAL_STATUSES.map((s) => (
))}
)}
{hasActiveFilters && (
)}
{err && (
{err}
)}
Proposal #
Customer
WO #
Category
Priority
Status
Bid Amount
Submitted
Submitted By
Age
{loading
? Array.from({ length: 5 }).map((_, i) => (
{Array.from({ length: COL_COUNT }).map((_, j) => (
))}
))
: grouped.map((group) => {
const isExpanded = expanded.has(group.baseNumber);
return (
{renderRow(group.latest, false, group.hasRevisions, isExpanded, group.baseNumber)}
{group.hasRevisions && (
{group.revisions.map((rev) =>
renderRow(rev, true, false, false, group.baseNumber)
)}
)}
);
})}
{!loading && rows.length === 0 && (
{hasActiveFilters
? 'No proposals match your current filters.'
: 'No proposals in queue.'}
{hasActiveFilters && (
)}
)}
setPage(newPage + 1)}
rowsPerPage={pageSize}
onRowsPerPageChange={(e) => setPageSize(parseInt(e.target.value, 10))}
rowsPerPageOptions={[12, 24, 48]}
/>
);
}