mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 15:08:58 +00:00
refactor(web): dashboards on domain layer
This commit is contained in:
parent
b7c6386aca
commit
d1c7e812f6
2 changed files with 49 additions and 34 deletions
|
|
@ -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<DashboardStats>({
|
||||
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<Set<string>>(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<ProposalListItem>(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);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{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);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{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);
|
||||
}}
|
||||
>
|
||||
<MenuItem value="">All</MenuItem>
|
||||
{PROPOSAL_STATUSES.map((s) => (
|
||||
|
|
|
|||
|
|
@ -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<ProposalStats>({
|
||||
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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue