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)} > All {SERVICE_CATEGORIES.map((c) => ( {c} ))} setPriorityFilter(e.target.value)} > All {PRIORITIES.map((p) => ( {PRIORITY_LABELS[p] || p} ))} {!showTabs && ( setStatusFilter(e.target.value)} > All {PROPOSAL_STATUSES.map((s) => ( {STATUS_LABELS[s] || 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]} />
); }