From d1c7e812f6d7a8b79e2ed5734e71406157dc9f0e Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Mon, 13 Jul 2026 18:03:33 -0400 Subject: [PATCH] refactor(web): dashboards on domain layer --- .../pages/admin/dashboard/AdminDashboard.tsx | 69 ++++++++++++------- web/src/pages/dashboard/Dashboard.tsx | 14 +--- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/web/src/pages/admin/dashboard/AdminDashboard.tsx b/web/src/pages/admin/dashboard/AdminDashboard.tsx index b664728..d0d0bce 100644 --- a/web/src/pages/admin/dashboard/AdminDashboard.tsx +++ b/web/src/pages/admin/dashboard/AdminDashboard.tsx @@ -1,6 +1,5 @@ -import { useMemo, useState, Fragment } from 'react'; +import { useEffect, useMemo, useState, Fragment } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; import { Box, Typography, @@ -37,11 +36,10 @@ import ReportIcon from '@mui/icons-material/Report'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutlined'; 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_CHIP_STYLES, STATUS_LABELS, PRIORITY_LABELS, PRIORITY_CHIP_STYLES, PRIORITY_ROW_SX } from '../../../constants'; +import { useProposals } from '../../../domain/proposals/use-cases'; +import type { ProposalListItem } from '../../../domain/proposals/types'; +import { useAdminDashboard } from '../../../domain/admin/use-cases'; +import { DEBOUNCE_SEARCH, DEFAULT_PAGE, DEFAULT_PAGE_SIZE, SERVICE_CATEGORIES, PRIORITIES, PROPOSAL_STATUSES, STATUS_CHIP_STYLES, STATUS_LABELS, PRIORITY_LABELS, PRIORITY_CHIP_STYLES, PRIORITY_ROW_SX } from '../../../constants'; import { formatBidAmount, formatDate, formatDateFull, formatAge, getAgeColor, groupRevisions } from '../../../lib/format'; import { useDocumentTitle } from '../../../hooks/useDocumentTitle'; @@ -75,33 +73,48 @@ export default function AdminDashboard({ defaultStatus }: { defaultStatus?: stri const showTabs = !defaultStatus; // Fix: WEB-M8 — capture error state for admin stats to show error instead of misleading zeros - const { data: stats, isLoading: statsLoading, isError: statsError, refetch: refetchStats } = useQuery({ - queryKey: [QUERY_KEYS.dashboard], - queryFn: adminApi.getDashboard, - }); + const { data: stats, isLoading: statsLoading, isError: statsError, refetch: refetchStats } = useAdminDashboard(); const [statusFilter, setStatusFilter] = useState(defaultStatus ?? ''); const [categoryFilter, setCategoryFilter] = useState(''); const [priorityFilter, setPriorityFilter] = useState(''); const [expanded, setExpanded] = useState>(new Set()); + const [search, setSearch] = useState(''); + const [debouncedSearch, setDebouncedSearch] = useState(''); + const [page, setPage] = useState(DEFAULT_PAGE); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + + // Debounced search resets to page 1 (mirrors the old usePaginatedList behavior). + useEffect(() => { + const handle = setTimeout(() => { + setDebouncedSearch(search); + setPage(1); + }, DEBOUNCE_SEARCH); + return () => clearTimeout(handle); + }, [search]); + const { - rows, - search, - setSearch, + data: listData, + isPending: listPending, + isPlaceholderData: listIsPlaceholder, + error: listError, + } = useProposals({ + search: debouncedSearch, page, - setPage, pageSize, - setPageSize, - totalCount, - loading, - err, - } = usePaginatedList(proposalsApi.getAll, { ...(statusFilter && { status: statusFilter }), ...(categoryFilter && { serviceCategory: categoryFilter }), ...(priorityFilter && { priority: priorityFilter }), }); + const rows = useMemo(() => listData?.items ?? [], [listData]); + const totalCount = listData?.totalCount ?? 0; + // Show skeletons on initial load and while previous-page placeholder data is + // displayed for a new filter/page/search key (matches the old loading flag). + const loading = listPending || listIsPlaceholder; + const err = listError ? listError.message || 'Failed to load' : ''; + const grouped = useMemo(() => groupRevisions(rows), [rows]); const toggleExpand = (baseNumber: string) => { @@ -127,6 +140,7 @@ export default function AdminDashboard({ defaultStatus }: { defaultStatus?: stri setCategoryFilter(''); setPriorityFilter(''); setSearch(''); + setPage(1); }; const renderRow = (row: ProposalListItem, indent: boolean, showExpandIcon: boolean, isExpanded: boolean, baseNumber: string) => ( @@ -297,7 +311,10 @@ export default function AdminDashboard({ defaultStatus }: { defaultStatus?: stri label="Category" value={categoryFilter} sx={{ width: 160 }} - onChange={(e) => setCategoryFilter(e.target.value)} + onChange={(e) => { + setCategoryFilter(e.target.value); + setPage(1); + }} > All {SERVICE_CATEGORIES.map((c) => ( @@ -310,7 +327,10 @@ export default function AdminDashboard({ defaultStatus }: { defaultStatus?: stri label="Priority" value={priorityFilter} sx={{ width: 200 }} - onChange={(e) => setPriorityFilter(e.target.value)} + onChange={(e) => { + setPriorityFilter(e.target.value); + setPage(1); + }} > All {PRIORITIES.map((p) => ( @@ -324,7 +344,10 @@ export default function AdminDashboard({ defaultStatus }: { defaultStatus?: stri label="Status" value={statusFilter} sx={{ width: 140 }} - onChange={(e) => setStatusFilter(e.target.value)} + onChange={(e) => { + setStatusFilter(e.target.value); + setPage(1); + }} > All {PROPOSAL_STATUSES.map((s) => ( diff --git a/web/src/pages/dashboard/Dashboard.tsx b/web/src/pages/dashboard/Dashboard.tsx index 56931a9..d6ed031 100644 --- a/web/src/pages/dashboard/Dashboard.tsx +++ b/web/src/pages/dashboard/Dashboard.tsx @@ -1,5 +1,4 @@ import { useNavigate } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; import { Box, Typography, @@ -24,8 +23,7 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import SendIcon from '@mui/icons-material/Send'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutlined'; import Tooltip from '@mui/material/Tooltip'; -import { proposalsApi, type ProposalListItem, type ProposalStats } from '../../lib/api/proposals'; -import { QUERY_KEYS } from '../../constants/queryKeys'; +import { useProposals, useProposalStats } from '../../domain/proposals/use-cases'; import { STATUS_CHIP_STYLES } from '../../constants'; import { formatCurrency, formatDate, formatDateFull } from '../../lib/format'; import { useDocumentTitle } from '../../hooks/useDocumentTitle'; @@ -55,16 +53,10 @@ export default function Dashboard() { useDocumentTitle('Dashboard'); const navigate = useNavigate(); - const { data: recentData, isLoading: recentLoading, error: recentError } = useQuery<{ items: ProposalListItem[]; totalCount: number }>({ - queryKey: [QUERY_KEYS.proposals, 'dashboard-recent'], - queryFn: () => proposalsApi.getAll({ page: 1, pageSize: 5, mine: true }), - }); + const { data: recentData, isLoading: recentLoading, isError: recentError } = useProposals({ page: 1, pageSize: 5, mine: true }); // Fix: WEB-M8 — capture error state for stats to show error instead of misleading zeros - const { data: stats, isLoading: statsLoading, isError: statsError, refetch: refetchStats } = useQuery({ - queryKey: [QUERY_KEYS.proposals, 'stats'], - queryFn: () => proposalsApi.getStats(), - }); + const { data: stats, isLoading: statsLoading, isError: statsError, refetch: refetchStats } = useProposalStats(); const proposals = recentData?.items ?? []; const total = stats?.totalCount ?? 0;