From 36d9fcdc6c06d4a9bee13a75d1e90dff5e292ea5 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Tue, 11 Aug 2026 15:40:25 -0400 Subject: [PATCH] feat(web): align filter toolbar layout, field shapes, and tabs Refs: AP-43 --- src/dashboard/DashboardGrids.tsx | 108 +++++++++++++---- src/dashboard/InvoiceFilterToolbar.tsx | 156 +++++++++++++------------ src/dashboard/invoice-tabs.tsx | 40 +++++++ 3 files changed, 209 insertions(+), 95 deletions(-) create mode 100644 src/dashboard/invoice-tabs.tsx diff --git a/src/dashboard/DashboardGrids.tsx b/src/dashboard/DashboardGrids.tsx index ba23661..e7d8a55 100644 --- a/src/dashboard/DashboardGrids.tsx +++ b/src/dashboard/DashboardGrids.tsx @@ -10,6 +10,7 @@ import { readyToPayColumns, } from "@/dashboard/columns"; import { EMPTY_INVOICE_FILTERS, type InvoiceFilterState } from "@/dashboard/invoice-filters"; +import { InvoiceTabLabel } from "@/dashboard/invoice-tabs"; import { countSelectedRows } from "@/dashboard/selection"; import { useInvoices, useUpdateInvoicePayDate } from "@/domain/invoices/use-invoices"; import { areMocksEnabled, type MockInvoice } from "@/mocks"; @@ -24,6 +25,27 @@ const emptySelection: GridRowSelectionModel = { const emptyFilters = EMPTY_INVOICE_FILTERS; +const invoiceTabsSx = { + minHeight: 44, + "& .MuiTab-root": { + bgcolor: "rgba(47,128,237,0.12)", + mr: 0.5, + borderTopLeftRadius: 6, + borderTopRightRadius: 6, + color: "text.primary", + textTransform: "none", + }, + "& .Mui-selected": { + bgcolor: "#fff !important", + color: "text.primary !important", + }, + "& .MuiTabs-indicator": { + height: 3, + borderRadius: 1, + backgroundColor: "#7B61FF", + }, +} as const; + const rowClickSx = { border: 1, borderColor: "divider", @@ -134,47 +156,87 @@ export function DashboardGrids() { }} variant="scrollable" aria-label="Invoice Processing tabs" - sx={{ - minHeight: 44, - "& .MuiTab-root": { - bgcolor: "rgba(47,128,237,0.12)", - mr: 0.5, - borderTopLeftRadius: 6, - borderTopRightRadius: 6, - color: "text.primary", - }, - "& .Mui-selected": { - bgcolor: "#fff !important", - color: "text.primary !important", - }, - "& .MuiTabs-indicator": { display: "none" }, - }} + sx={invoiceTabsSx} > + } value="attention" data-testid="tab-pending" /> - + } + value="all" + data-testid="tab-all" + /> + + } value="approved" data-testid="tab-ready" /> + } value="rejected" data-testid="tab-rejected" /> - + + } + value="late" + data-testid="tab-late" + /> {attentionRows.length > 0 && tab === "attention" ? ( - - {attentionRows.length} invoices require approval. Show Invoices ready to approve. - + + {attentionRows.length} invoices require approval. Show invoices ready to approve. + ) : null} - - + + + onFiltersChange({ ...filters, action: value })} + renderInput={(params) => ( + + )} + /> + + onFiltersChange({ ...filters, action: value })} - renderInput={(params) => } + name="vendor" + id="vendor" + placeholder="Vendor" + value={filters.vendorQuery} + onChange={(event) => onFiltersChange({ ...filters, vendorQuery: event.target.value })} + sx={fieldSx} + slotProps={{ + htmlInput: { "data-testid": "vendor-filter", "aria-label": "Vendor" }, + input: { + endAdornment: filters.vendorQuery ? ( + + + + ) : undefined, + }, + }} /> - - onFiltersChange({ ...filters, vendorQuery: event.target.value })} - sx={{ minWidth: 180 }} - slotProps={{ - htmlInput: { "data-testid": "vendor-filter" }, - input: { - endAdornment: filters.vendorQuery ? ( - - - - ) : undefined, - }, - }} - /> - - - - - - - - - - - - - - {trailingActions} + + + + + + + + + + + + + + + + + {trailingActions} + ); } diff --git a/src/dashboard/invoice-tabs.tsx b/src/dashboard/invoice-tabs.tsx new file mode 100644 index 0000000..6e18d1f --- /dev/null +++ b/src/dashboard/invoice-tabs.tsx @@ -0,0 +1,40 @@ +import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; +import { Box, Stack } from "@mui/material"; +import type { ReactElement } from "react"; + +export function InvoiceTabLabel({ + title, + count, + active = false, + urgency = false, +}: { + title: string; + count: number; + active?: boolean; + urgency?: boolean; +}): ReactElement { + return ( + + {title} + {urgency ? : null} + + {count} + + + ); +}