diff --git a/e2e/dashboard-grids.spec.ts b/e2e/dashboard-grids.spec.ts
index 29bc24c..7089c70 100644
--- a/e2e/dashboard-grids.spec.ts
+++ b/e2e/dashboard-grids.spec.ts
@@ -53,6 +53,23 @@ test.describe("dashboard grids", () => {
/Pay run stub: [2-9]\d* invoice\(s\) selected/,
);
+ await page.getByTestId("columns-toggle").click();
+ const drawer = page.getByTestId("columns-drawer");
+ await drawer.getByTestId("columns-hidden-search").fill("Payment Date");
+ await drawer
+ .getByTestId("columns-hidden-paymentDate")
+ .locator('input[type="checkbox"]')
+ .evaluate((el: HTMLInputElement) => {
+ el.click();
+ });
+ await expect(
+ readyGrid.locator('.MuiDataGrid-columnHeader[data-field="payDate"]'),
+ ).toBeVisible();
+ await page.getByRole("button", { name: "Close columns" }).evaluate((el: HTMLElement) => {
+ el.click();
+ });
+ await expect(drawer).toBeHidden();
+
const payDateCell = readyGrid.locator('[data-field="payDate"]').nth(1);
await payDateCell.dblclick();
const editor = readyGrid
diff --git a/e2e/invoice-columns.spec.ts b/e2e/invoice-columns.spec.ts
new file mode 100644
index 0000000..e2853cb
--- /dev/null
+++ b/e2e/invoice-columns.spec.ts
@@ -0,0 +1,64 @@
+import { expect, test } from "@playwright/test";
+
+test.describe("Invoice Processing columns drawer (AP-45)", () => {
+ test("Columns drawer can hide Tagging and Reset restores it", async ({ page }) => {
+ await page.goto("/");
+ await expect(
+ page.getByTestId("grid-pending").locator('.MuiDataGrid-columnHeader[data-field="tagging"]'),
+ ).toBeVisible();
+
+ await page.getByTestId("columns-toggle").click();
+ const drawer = page.getByTestId("columns-drawer");
+ await expect(drawer).toBeVisible();
+
+ const taggingRow = drawer.getByTestId("columns-visible-tagging");
+ await taggingRow.scrollIntoViewIfNeeded();
+ await taggingRow.locator('input[type="checkbox"]').evaluate((el: HTMLInputElement) => {
+ el.click();
+ });
+ await expect(
+ page.getByTestId("grid-pending").locator('.MuiDataGrid-columnHeader[data-field="tagging"]'),
+ ).toHaveCount(0);
+
+ await drawer.getByTestId("columns-selector-reset-button").evaluate((el: HTMLElement) => {
+ el.click();
+ });
+ await expect(
+ page.getByTestId("grid-pending").locator('.MuiDataGrid-columnHeader[data-field="tagging"]'),
+ ).toBeVisible();
+ });
+
+ test("locked Vendor column stays visible and Reset clears the hidden search", async ({
+ page,
+ }) => {
+ await page.goto("/");
+ await page.getByTestId("columns-toggle").click();
+ const drawer = page.getByTestId("columns-drawer");
+ const vendorBox = drawer
+ .getByTestId("columns-visible-vendor")
+ .locator('input[type="checkbox"]');
+ await expect(vendorBox).toBeDisabled();
+ await vendorBox.evaluate((el: HTMLInputElement) => {
+ el.disabled = false;
+ el.click();
+ });
+ await expect(
+ page
+ .getByTestId("grid-pending")
+ .locator('.MuiDataGrid-columnHeader[data-field="vendorName"]'),
+ ).toBeVisible();
+ await page.getByTestId("tab-ready").evaluate((el: HTMLElement) => {
+ el.click();
+ });
+ await expect(
+ page.getByTestId("grid-ready").locator('.MuiDataGrid-columnHeader[data-field="vendorName"]'),
+ ).toBeVisible();
+
+ const search = drawer.getByTestId("columns-hidden-search");
+ await search.fill("no-such-column");
+ await drawer.getByTestId("columns-selector-reset-button").evaluate((el: HTMLElement) => {
+ el.click();
+ });
+ await expect(search).toHaveValue("");
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index d100c79..7fa5aaf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,9 @@
"packages/*"
],
"dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fontsource/ibm-plex-sans": "^5.3.0",
@@ -842,6 +845,59 @@
"node": ">=20.19.0"
}
},
+ "node_modules/@dnd-kit/accessibility": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
+ "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/core": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
+ "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/accessibility": "^3.1.1",
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/sortable": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
+ "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.3.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/utilities": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
+ "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/@drizzle-team/brocli": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
diff --git a/package.json b/package.json
index 440cfe0..7477812 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,9 @@
},
"packageManager": "npm@12.0.2",
"dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fontsource/ibm-plex-sans": "^5.3.0",
diff --git a/src/dashboard/ColumnsDrawer.tsx b/src/dashboard/ColumnsDrawer.tsx
new file mode 100644
index 0000000..36b6d46
--- /dev/null
+++ b/src/dashboard/ColumnsDrawer.tsx
@@ -0,0 +1,207 @@
+import {
+ DndContext,
+ PointerSensor,
+ closestCenter,
+ type DragEndEvent,
+ useSensor,
+ useSensors,
+} from "@dnd-kit/core";
+import {
+ SortableContext,
+ arrayMove,
+ useSortable,
+ verticalListSortingStrategy,
+} from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import CloseIcon from "@mui/icons-material/Close";
+import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
+import {
+ Box,
+ Button,
+ Checkbox,
+ Drawer,
+ FormControlLabel,
+ IconButton,
+ Stack,
+ TextField,
+ Typography,
+} from "@mui/material";
+import type { CSSProperties, ReactElement } from "react";
+import { useMemo, useState } from "react";
+import type { InvoiceColumnKey } from "@/dashboard/column-catalog";
+import {
+ catalogLabel,
+ hiddenColumnKeys,
+ isColumnLocked,
+ type ColumnPrefs,
+} from "@/dashboard/column-prefs";
+
+type ColumnsDrawerProps = {
+ open: boolean;
+ onClose: () => void;
+ prefs: ColumnPrefs;
+ onPrefsChange: (next: ColumnPrefs) => void;
+ onReset: () => void;
+};
+
+function SortableVisibleRow({
+ columnKey,
+ onToggle,
+}: {
+ columnKey: InvoiceColumnKey;
+ onToggle: (key: InvoiceColumnKey, checked: boolean) => void;
+}): ReactElement {
+ const locked = isColumnLocked(columnKey);
+ const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
+ id: columnKey,
+ });
+ const style: CSSProperties = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ display: "flex",
+ alignItems: "center",
+ gap: 8,
+ };
+
+ return (
+
+
+
+
+ onToggle(columnKey, event.target.checked)}
+ />
+ }
+ label={catalogLabel(columnKey)}
+ />
+
+ );
+}
+
+export function ColumnsDrawer({
+ open,
+ onClose,
+ prefs,
+ onPrefsChange,
+ onReset,
+}: ColumnsDrawerProps): ReactElement {
+ const [hiddenQuery, setHiddenQuery] = useState("");
+ const [viewAllHidden, setViewAllHidden] = useState(false);
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }));
+
+ const hidden = useMemo(() => hiddenColumnKeys(prefs.visibleOrder), [prefs.visibleOrder]);
+ const filteredHidden = useMemo(() => {
+ const q = hiddenQuery.trim().toLowerCase();
+ const list = q ? hidden.filter((key) => catalogLabel(key).toLowerCase().includes(q)) : hidden;
+ if (viewAllHidden || q) {
+ return list;
+ }
+ return list.slice(0, 5);
+ }, [hidden, hiddenQuery, viewAllHidden]);
+
+ const onDragEnd = (event: DragEndEvent) => {
+ const { active, over } = event;
+ if (!over || active.id === over.id) {
+ return;
+ }
+ const oldIndex = prefs.visibleOrder.indexOf(active.id as InvoiceColumnKey);
+ const newIndex = prefs.visibleOrder.indexOf(over.id as InvoiceColumnKey);
+ if (oldIndex < 0 || newIndex < 0) {
+ return;
+ }
+ onPrefsChange({ visibleOrder: arrayMove(prefs.visibleOrder, oldIndex, newIndex) });
+ };
+
+ const onToggle = (key: InvoiceColumnKey, checked: boolean) => {
+ if (isColumnLocked(key)) {
+ return;
+ }
+ if (checked) {
+ if (prefs.visibleOrder.includes(key)) {
+ return;
+ }
+ onPrefsChange({ visibleOrder: [...prefs.visibleOrder, key] });
+ return;
+ }
+ onPrefsChange({ visibleOrder: prefs.visibleOrder.filter((item) => item !== key) });
+ };
+
+ return (
+
+
+
+ Columns
+
+
+
+
+
+
+
+
+
+
+ Visible
+
+
+
+
+ {prefs.visibleOrder.map((key) => (
+
+ ))}
+
+
+
+
+
+ Hidden
+
+ setHiddenQuery(event.target.value)}
+ sx={{ mb: 1 }}
+ slotProps={{ htmlInput: { "data-testid": "columns-hidden-search" } }}
+ />
+
+ {filteredHidden.map((key) => (
+ onToggle(key, event.target.checked)} />
+ }
+ label={catalogLabel(key)}
+ />
+ ))}
+
+ {!viewAllHidden && !hiddenQuery && hidden.length > 5 ? (
+
+ ) : null}
+
+ );
+}
diff --git a/src/dashboard/DashboardGrids.tsx b/src/dashboard/DashboardGrids.tsx
index 9d767fa..ab1f03d 100644
--- a/src/dashboard/DashboardGrids.tsx
+++ b/src/dashboard/DashboardGrids.tsx
@@ -1,14 +1,17 @@
import { useMemo, useState } from "react";
import { Alert, Box, CircularProgress, Stack, Tab, Tabs, Typography } from "@mui/material";
import type { GridRowParams, GridRowSelectionModel } from "@mui/x-data-grid";
+import { ColumnsDrawer } from "@/dashboard/ColumnsDrawer";
import { InvoiceFilterToolbar } from "@/dashboard/InvoiceFilterToolbar";
import { CreatePayRunButton } from "@/dashboard/CreatePayRunButton";
import { InvoiceDataGrid } from "@/dashboard/InvoiceDataGrid";
+import { columnsForApprovedGrid, columnsFromCatalogKeys } from "@/dashboard/columns";
import {
- allInvoicesColumns,
- invoiceAttentionColumns,
- readyToPayColumns,
-} from "@/dashboard/columns";
+ loadColumnPrefs,
+ resetColumnPrefs,
+ saveColumnPrefs,
+ type ColumnPrefs,
+} from "@/dashboard/column-prefs";
import { EMPTY_INVOICE_FILTERS, type InvoiceFilterState } from "@/dashboard/invoice-filters";
import { InvoiceTabLabel } from "@/dashboard/invoice-tabs";
import { countSelectedRows } from "@/dashboard/selection";
@@ -72,11 +75,21 @@ export function DashboardGrids() {
const [tab, setTab] = useState("attention");
const [selection, setSelection] = useState(emptySelection);
const [filters, setFilters] = useState(emptyFilters);
- const [, setColumnsDrawerOpen] = useState(false);
+ const [columnsDrawerOpen, setColumnsDrawerOpen] = useState(false);
const [, setFilterDrawerOpen] = useState(false);
+ const [columnPrefs, setColumnPrefs] = useState(() => loadColumnPrefs());
const { data: invoices = [], isLoading, isError, error } = useInvoices();
const updatePayDate = useUpdateInvoicePayDate();
+ const catalogColumns = useMemo(
+ () => columnsFromCatalogKeys(columnPrefs.visibleOrder),
+ [columnPrefs.visibleOrder],
+ );
+ const approvedColumns = useMemo(
+ () => columnsForApprovedGrid(columnPrefs.visibleOrder),
+ [columnPrefs.visibleOrder],
+ );
+
const openInvoice = (params: GridRowParams) => {
navigate("/invoices/:id", { params: { id: params.row.id } });
};
@@ -129,6 +142,11 @@ export function DashboardGrids() {
return updatePayDate.mutateAsync({ id: newRow.id, payDate: nextPayDate });
};
+ const updateColumnPrefs = (next: ColumnPrefs) => {
+ setColumnPrefs(next);
+ saveColumnPrefs(next);
+ };
+
if (isLoading) {
return (
@@ -245,7 +263,7 @@ export function DashboardGrids() {
@@ -255,7 +273,7 @@ export function DashboardGrids() {
@@ -265,7 +283,7 @@ export function DashboardGrids() {
setSelection(model)}
@@ -281,7 +299,7 @@ export function DashboardGrids() {
@@ -291,7 +309,7 @@ export function DashboardGrids() {
@@ -301,6 +319,17 @@ export function DashboardGrids() {
Mocks {areMocksEnabled() ? "enabled" : "disabled"} — {invoices.length} invoices
+
+ setColumnsDrawerOpen(false)}
+ prefs={columnPrefs}
+ onPrefsChange={updateColumnPrefs}
+ onReset={() => {
+ const next = resetColumnPrefs();
+ setColumnPrefs(next);
+ }}
+ />
);
}
diff --git a/src/dashboard/approved-columns.ts b/src/dashboard/approved-columns.ts
new file mode 100644
index 0000000..b94e671
--- /dev/null
+++ b/src/dashboard/approved-columns.ts
@@ -0,0 +1,9 @@
+import type { GridColDef } from "@mui/x-data-grid";
+import type { MockInvoice } from "@/mocks";
+
+/** Mark a visible pay-date column editable. A hidden Payment Date stays omitted. */
+export function withEditablePayDate(columns: GridColDef[]): GridColDef[] {
+ return columns.map((column) =>
+ column.field === "payDate" ? { ...column, editable: true } : column,
+ );
+}
diff --git a/src/dashboard/column-prefs.test.ts b/src/dashboard/column-prefs.test.ts
new file mode 100644
index 0000000..c1ba789
--- /dev/null
+++ b/src/dashboard/column-prefs.test.ts
@@ -0,0 +1,42 @@
+import { afterEach, describe, expect, it } from "vitest";
+import {
+ COLUMN_PREFS_STORAGE_KEY,
+ defaultColumnPrefs,
+ hiddenColumnKeys,
+ loadColumnPrefs,
+ resetColumnPrefs,
+ saveColumnPrefs,
+} from "@/dashboard/column-prefs";
+import { DEFAULT_VISIBLE_COLUMN_KEYS } from "@/dashboard/column-catalog";
+
+describe("column-prefs", () => {
+ afterEach(() => {
+ sessionStorage.removeItem(COLUMN_PREFS_STORAGE_KEY);
+ });
+
+ it("defaults to HTML Visible order", () => {
+ expect(defaultColumnPrefs().visibleOrder).toEqual([...DEFAULT_VISIBLE_COLUMN_KEYS]);
+ });
+
+ it("persists and reloads prefs", () => {
+ saveColumnPrefs({ visibleOrder: ["invoiceId", "vendor", "dueDate"] });
+ expect(loadColumnPrefs().visibleOrder).toEqual([
+ "bolt",
+ "bell",
+ "invoiceId",
+ "vendor",
+ "dueDate",
+ ]);
+ });
+
+ it("reset restores defaults", () => {
+ saveColumnPrefs({ visibleOrder: ["invoiceId"] });
+ expect(resetColumnPrefs().visibleOrder).toEqual([...DEFAULT_VISIBLE_COLUMN_KEYS]);
+ });
+
+ it("computes hidden keys from visible order", () => {
+ const hidden = hiddenColumnKeys(["invoiceId", "vendor"]);
+ expect(hidden).toContain("tagging");
+ expect(hidden).not.toContain("invoiceId");
+ });
+});
diff --git a/src/dashboard/column-prefs.ts b/src/dashboard/column-prefs.ts
new file mode 100644
index 0000000..da750ea
--- /dev/null
+++ b/src/dashboard/column-prefs.ts
@@ -0,0 +1,66 @@
+import {
+ DEFAULT_VISIBLE_COLUMN_KEYS,
+ INVOICE_COLUMN_CATALOG,
+ LOCKED_COLUMN_KEYS,
+ type InvoiceColumnKey,
+} from "@/dashboard/column-catalog";
+
+export const COLUMN_PREFS_STORAGE_KEY = "seahaven-ap.invoice-columns.v1";
+
+export type ColumnPrefs = {
+ visibleOrder: InvoiceColumnKey[];
+};
+
+export function defaultColumnPrefs(): ColumnPrefs {
+ return { visibleOrder: [...DEFAULT_VISIBLE_COLUMN_KEYS] };
+}
+
+export function loadColumnPrefs(): ColumnPrefs {
+ if (typeof sessionStorage === "undefined") {
+ return defaultColumnPrefs();
+ }
+ try {
+ const raw = sessionStorage.getItem(COLUMN_PREFS_STORAGE_KEY);
+ if (!raw) {
+ return defaultColumnPrefs();
+ }
+ const parsed = JSON.parse(raw) as ColumnPrefs;
+ if (!Array.isArray(parsed.visibleOrder) || parsed.visibleOrder.length === 0) {
+ return defaultColumnPrefs();
+ }
+ const known = new Set(INVOICE_COLUMN_CATALOG.map((entry) => entry.key));
+ const visibleOrder = parsed.visibleOrder.filter((key) => known.has(key));
+ const missingLocked = LOCKED_COLUMN_KEYS.filter((key) => !visibleOrder.includes(key));
+ return { visibleOrder: [...missingLocked, ...visibleOrder] };
+ } catch {
+ return defaultColumnPrefs();
+ }
+}
+
+export function saveColumnPrefs(prefs: ColumnPrefs): void {
+ if (typeof sessionStorage === "undefined") {
+ return;
+ }
+ sessionStorage.setItem(COLUMN_PREFS_STORAGE_KEY, JSON.stringify(prefs));
+}
+
+export function resetColumnPrefs(): ColumnPrefs {
+ const prefs = defaultColumnPrefs();
+ saveColumnPrefs(prefs);
+ return prefs;
+}
+
+export function hiddenColumnKeys(visibleOrder: InvoiceColumnKey[]): InvoiceColumnKey[] {
+ const visible = new Set(visibleOrder);
+ return INVOICE_COLUMN_CATALOG.filter((entry) => !visible.has(entry.key)).map(
+ (entry) => entry.key,
+ );
+}
+
+export function isColumnLocked(key: InvoiceColumnKey): boolean {
+ return LOCKED_COLUMN_KEYS.includes(key);
+}
+
+export function catalogLabel(key: InvoiceColumnKey): string {
+ return INVOICE_COLUMN_CATALOG.find((entry) => entry.key === key)?.label ?? key;
+}
diff --git a/src/dashboard/columns.test.ts b/src/dashboard/columns.test.ts
index a0cdeb1..a3eb9d3 100644
--- a/src/dashboard/columns.test.ts
+++ b/src/dashboard/columns.test.ts
@@ -1,4 +1,7 @@
+import type { GridColDef } from "@mui/x-data-grid";
import { describe, expect, it } from "vitest";
+import { withEditablePayDate } from "@/dashboard/approved-columns";
+import type { MockInvoice } from "@/mocks";
import { formatRelativeFromIsoDate, formatSlashDate } from "@/dashboard/invoice-date-format";
describe("invoice column date helpers (AP-47)", () => {
@@ -26,3 +29,20 @@ describe("invoice column date helpers (AP-47)", () => {
expect(formatRelativeFromIsoDate("2026-10-11", now)).toBe("in 2 months");
});
});
+
+describe("withEditablePayDate", () => {
+ const hidden: GridColDef = { field: "vendorName", headerName: "Vendor" };
+ const payDate: GridColDef = { field: "payDate", headerName: "Payment Date" };
+
+ it("leaves Payment Date hidden when the catalog omits it", () => {
+ const columns = withEditablePayDate([hidden]);
+ expect(columns.some((column) => column.field === "payDate")).toBe(false);
+ });
+
+ it("keeps a single editable pay date when Payment Date is already visible", () => {
+ const columns = withEditablePayDate([hidden, { ...payDate, editable: false }]);
+ const matches = columns.filter((column) => column.field === "payDate");
+ expect(matches).toHaveLength(1);
+ expect(matches[0]?.editable).toBe(true);
+ });
+});
diff --git a/src/dashboard/columns.tsx b/src/dashboard/columns.tsx
index 3b1ded2..c720972 100644
--- a/src/dashboard/columns.tsx
+++ b/src/dashboard/columns.tsx
@@ -4,6 +4,7 @@ import NotificationsNoneOutlinedIcon from "@mui/icons-material/NotificationsNone
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
import { Box, Link as MuiLink, Stack, Typography } from "@mui/material";
import type { GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
+import { withEditablePayDate } from "@/dashboard/approved-columns";
import type { InvoiceColumnKey } from "@/dashboard/column-catalog";
import { formatRelativeFromIsoDate, formatSlashDate } from "@/dashboard/invoice-date-format";
import type { MockInvoice } from "@/mocks";
@@ -250,6 +251,11 @@ export function columnsFromCatalogKeys(keys: InvoiceColumnKey[]): GridColDef catalogColumnDefs[key]);
}
+/** Approved tab keeps pay date editable when the drawer shows Payment Date. */
+export function columnsForApprovedGrid(keys: InvoiceColumnKey[]): GridColDef[] {
+ return withEditablePayDate(columnsFromCatalogKeys(keys));
+}
+
export const readyToPayColumns: GridColDef[] = [
{
field: "invoiceNumber",