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 { 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"; import { useInvoices, useUpdateInvoicePayDate } from "@/domain/invoices/use-invoices"; import { areMocksEnabled, type MockInvoice } from "@/mocks"; import { useNavigate } from "@/router"; type InvoiceTab = "attention" | "all" | "approved" | "rejected" | "late"; const emptySelection: GridRowSelectionModel = { type: "include", ids: new Set(), }; const emptyFilters = EMPTY_INVOICE_FILTERS; const rowClickSx = { border: 1, borderColor: "divider", backgroundColor: "background.paper", cursor: "pointer", borderRadius: 1, } as const; 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; function applyInvoiceFilters(rows: MockInvoice[], filters: InvoiceFilterState): MockInvoice[] { const vendorQuery = filters.vendorQuery.trim().toLowerCase(); return rows.filter((invoice) => { if (filters.action && invoice.actionLabel !== filters.action) { return false; } if (vendorQuery && !invoice.vendorName.toLowerCase().includes(vendorQuery)) { return false; } return true; }); } export function DashboardGrids() { const navigate = useNavigate(); const [tab, setTab] = useState("attention"); const [selection, setSelection] = useState(emptySelection); const [filters, setFilters] = useState(emptyFilters); 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 } }); }; const attentionRows = useMemo( () => invoices.filter((invoice) => invoice.status === "pending_approval"), [invoices], ); const approvedRows = useMemo( () => invoices.filter((invoice) => invoice.status === "approved"), [invoices], ); const rejectedRows = useMemo( () => invoices.filter((invoice) => invoice.status === "rejected"), [invoices], ); const lateRows = useMemo(() => { const today = new Date().toISOString().slice(0, 10); return invoices.filter( (invoice) => invoice.dueDate < today && invoice.status !== "paid" && invoice.status !== "rejected", ); }, [invoices]); const filteredAttention = useMemo( () => applyInvoiceFilters(attentionRows, filters), [attentionRows, filters], ); const filteredAll = useMemo(() => applyInvoiceFilters(invoices, filters), [invoices, filters]); const filteredApproved = useMemo( () => applyInvoiceFilters(approvedRows, filters), [approvedRows, filters], ); const filteredRejected = useMemo( () => applyInvoiceFilters(rejectedRows, filters), [rejectedRows, filters], ); const filteredLate = useMemo(() => applyInvoiceFilters(lateRows, filters), [lateRows, filters]); const selectedCount = countSelectedRows( selection, approvedRows.map((invoice) => invoice.id), ); const processRowUpdate = async (newRow: MockInvoice, oldRow: MockInvoice) => { if (newRow.payDate === oldRow.payDate) { return oldRow; } const nextPayDate = newRow.payDate?.trim() ? newRow.payDate.trim() : null; return updatePayDate.mutateAsync({ id: newRow.id, payDate: nextPayDate }); }; const updateColumnPrefs = (next: ColumnPrefs) => { setColumnPrefs(next); saveColumnPrefs(next); }; if (isLoading) { return ( ); } if (isError) { return ( {error instanceof Error ? error.message : "Failed to load invoices"} ); } return ( { setTab(value); setSelection(emptySelection); }} variant="scrollable" aria-label="Invoice Processing tabs" 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" /> {filteredAttention.length > 0 && tab === "attention" ? ( {filteredAttention.length} invoices require approval.{" "} Show invoices ready to approve. ) : null} setFilterDrawerOpen(true)} onOpenColumnsDrawer={() => setColumnsDrawerOpen(true)} trailingActions={ tab === "approved" ? : null } /> {tab === "attention" && ( )} {tab === "all" && ( )} {tab === "approved" && ( setSelection(model)} processRowUpdate={processRowUpdate} onProcessRowUpdateError={(processError) => { console.error(processError); }} sx={rowClickSx} /> )} {tab === "rejected" && ( )} {tab === "late" && ( )} Mocks {areMocksEnabled() ? "enabled" : "disabled"} — {invoices.length} invoices setColumnsDrawerOpen(false)} prefs={columnPrefs} onPrefsChange={updateColumnPrefs} onReset={() => { const next = resetColumnPrefs(); setColumnPrefs(next); }} /> ); }