mirror of
https://github.com/Sea-Haven-Industries/seahaven-ap.git
synced 2026-10-07 04:41:56 +00:00
Merge 016d376efb into 56bfc5f84f
This commit is contained in:
commit
07eab62d78
3 changed files with 110 additions and 1 deletions
31
e2e/invoice-filter-drawer.spec.ts
Normal file
31
e2e/invoice-filter-drawer.spec.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
test.describe("Invoice Processing filter drawer (AP-46)", () => {
|
||||||
|
test("Filter drawer clears applied filters", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.getByTestId("vendor-filter").fill("Acme");
|
||||||
|
await page.getByTestId("filter-toggle").click();
|
||||||
|
const drawer = page.getByTestId("filter-drawer");
|
||||||
|
await expect(drawer).toBeVisible();
|
||||||
|
await expect(page.getByTestId("filter-drawer-vendor")).toContainText("Acme");
|
||||||
|
|
||||||
|
await page.getByTestId("filter-drawer-clear").click();
|
||||||
|
await expect(page.getByTestId("vendor-filter")).toHaveValue("");
|
||||||
|
await expect(page.getByTestId("filter-toggle").locator(".MuiBadge-badge")).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("closing the filter drawer leaves applied filters in place", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.getByTestId("vendor-filter").fill("Acme");
|
||||||
|
await page.getByTestId("filter-toggle").click();
|
||||||
|
await expect(page.getByTestId("filter-drawer")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("filter-toggle").locator(".MuiBadge-badge")).toHaveText("1");
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Close filters" }).evaluate((el: HTMLElement) => {
|
||||||
|
el.click();
|
||||||
|
});
|
||||||
|
await expect(page.getByTestId("filter-drawer")).toBeHidden();
|
||||||
|
await expect(page.getByTestId("vendor-filter")).toHaveValue("Acme");
|
||||||
|
await expect(page.getByTestId("filter-toggle").locator(".MuiBadge-badge")).toHaveText("1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -2,6 +2,7 @@ import { useMemo, useState } from "react";
|
||||||
import { Alert, Box, CircularProgress, Stack, Tab, Tabs, Typography } from "@mui/material";
|
import { Alert, Box, CircularProgress, Stack, Tab, Tabs, Typography } from "@mui/material";
|
||||||
import type { GridRowParams, GridRowSelectionModel } from "@mui/x-data-grid";
|
import type { GridRowParams, GridRowSelectionModel } from "@mui/x-data-grid";
|
||||||
import { ColumnsDrawer } from "@/dashboard/ColumnsDrawer";
|
import { ColumnsDrawer } from "@/dashboard/ColumnsDrawer";
|
||||||
|
import { FilterDrawer } from "@/dashboard/FilterDrawer";
|
||||||
import { InvoiceFilterToolbar } from "@/dashboard/InvoiceFilterToolbar";
|
import { InvoiceFilterToolbar } from "@/dashboard/InvoiceFilterToolbar";
|
||||||
import { CreatePayRunButton } from "@/dashboard/CreatePayRunButton";
|
import { CreatePayRunButton } from "@/dashboard/CreatePayRunButton";
|
||||||
import { InvoiceDataGrid } from "@/dashboard/InvoiceDataGrid";
|
import { InvoiceDataGrid } from "@/dashboard/InvoiceDataGrid";
|
||||||
|
|
@ -76,7 +77,7 @@ export function DashboardGrids() {
|
||||||
const [selection, setSelection] = useState<GridRowSelectionModel>(emptySelection);
|
const [selection, setSelection] = useState<GridRowSelectionModel>(emptySelection);
|
||||||
const [filters, setFilters] = useState<InvoiceFilterState>(emptyFilters);
|
const [filters, setFilters] = useState<InvoiceFilterState>(emptyFilters);
|
||||||
const [columnsDrawerOpen, setColumnsDrawerOpen] = useState(false);
|
const [columnsDrawerOpen, setColumnsDrawerOpen] = useState(false);
|
||||||
const [, setFilterDrawerOpen] = useState(false);
|
const [filterDrawerOpen, setFilterDrawerOpen] = useState(false);
|
||||||
const [columnPrefs, setColumnPrefs] = useState<ColumnPrefs>(() => loadColumnPrefs());
|
const [columnPrefs, setColumnPrefs] = useState<ColumnPrefs>(() => loadColumnPrefs());
|
||||||
const { data: invoices = [], isLoading, isError, error } = useInvoices();
|
const { data: invoices = [], isLoading, isError, error } = useInvoices();
|
||||||
const updatePayDate = useUpdateInvoicePayDate();
|
const updatePayDate = useUpdateInvoicePayDate();
|
||||||
|
|
@ -330,6 +331,12 @@ export function DashboardGrids() {
|
||||||
setColumnPrefs(next);
|
setColumnPrefs(next);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<FilterDrawer
|
||||||
|
open={filterDrawerOpen}
|
||||||
|
onClose={() => setFilterDrawerOpen(false)}
|
||||||
|
filters={filters}
|
||||||
|
onFiltersChange={setFilters}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
src/dashboard/FilterDrawer.tsx
Normal file
71
src/dashboard/FilterDrawer.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import { Box, Button, Drawer, IconButton, Stack, Typography } from "@mui/material";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
|
import { countActiveFilters, type InvoiceFilterState } from "@/dashboard/invoice-filters";
|
||||||
|
|
||||||
|
type FilterDrawerProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
filters: InvoiceFilterState;
|
||||||
|
onFiltersChange: (next: InvoiceFilterState) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FilterDrawer({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
filters,
|
||||||
|
onFiltersChange,
|
||||||
|
}: FilterDrawerProps): ReactElement {
|
||||||
|
const activeCount = countActiveFilters(filters);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
anchor="right"
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
data-testid="filter-drawer"
|
||||||
|
PaperProps={{ sx: { width: 360, p: 2 } }}
|
||||||
|
>
|
||||||
|
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ mb: 2 }}>
|
||||||
|
<Typography variant="h6">Filters</Typography>
|
||||||
|
<IconButton aria-label="Close filters" onClick={onClose} size="small">
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
|
{activeCount === 0
|
||||||
|
? "No filters applied."
|
||||||
|
: `${activeCount} active filter${activeCount === 1 ? "" : "s"}.`}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Stack spacing={1.5} sx={{ mb: 3 }}>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
Action
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" data-testid="filter-drawer-action">
|
||||||
|
{filters.action ?? "—"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
Vendor
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" data-testid="filter-drawer-vendor">
|
||||||
|
{filters.vendorQuery.trim() || "—"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
data-testid="filter-drawer-clear"
|
||||||
|
disabled={activeCount === 0}
|
||||||
|
onClick={() => onFiltersChange({ action: null, vendorQuery: "" })}
|
||||||
|
>
|
||||||
|
Clear all
|
||||||
|
</Button>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue