2026-08-11 15:20:30 -04:00
|
|
|
import BoltOutlinedIcon from "@mui/icons-material/BoltOutlined";
|
|
|
|
|
import NotificationsNoneOutlinedIcon from "@mui/icons-material/NotificationsNoneOutlined";
|
|
|
|
|
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
|
2026-08-10 18:22:22 -04:00
|
|
|
import { Link as MuiLink } from "@mui/material";
|
|
|
|
|
import type { GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
2026-08-11 15:20:30 -04:00
|
|
|
import type { InvoiceColumnKey } from "@/dashboard/column-catalog";
|
2026-08-10 17:32:43 -04:00
|
|
|
import type { MockInvoice } from "@/mocks";
|
2026-08-10 18:22:22 -04:00
|
|
|
import { Link } from "@/router";
|
2026-08-10 17:32:43 -04:00
|
|
|
|
|
|
|
|
const currencyFormatter = new Intl.NumberFormat("en-US", {
|
|
|
|
|
style: "currency",
|
|
|
|
|
currency: "USD",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const dateFormatter = new Intl.DateTimeFormat("en-US", {
|
|
|
|
|
year: "numeric",
|
|
|
|
|
month: "short",
|
|
|
|
|
day: "numeric",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function formatIsoDate(value: string | null | undefined): string {
|
|
|
|
|
if (!value) {
|
|
|
|
|
return "—";
|
|
|
|
|
}
|
|
|
|
|
const parsed = new Date(`${value}T00:00:00`);
|
|
|
|
|
if (Number.isNaN(parsed.getTime())) {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
return dateFormatter.format(parsed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatCurrency(value: number | null | undefined): string {
|
|
|
|
|
if (value == null) {
|
|
|
|
|
return "—";
|
|
|
|
|
}
|
|
|
|
|
return currencyFormatter.format(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const moneyColumn = (field: keyof MockInvoice, headerName: string): GridColDef<MockInvoice> => ({
|
|
|
|
|
field,
|
|
|
|
|
headerName,
|
|
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
type: "number",
|
|
|
|
|
valueFormatter: (value: number | null | undefined) => formatCurrency(value),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const dateColumn = (
|
|
|
|
|
field: keyof MockInvoice,
|
|
|
|
|
headerName: string,
|
|
|
|
|
editable = false,
|
|
|
|
|
): GridColDef<MockInvoice> => ({
|
|
|
|
|
field,
|
|
|
|
|
headerName,
|
|
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 130,
|
|
|
|
|
editable,
|
|
|
|
|
valueFormatter: (value: string | null | undefined) => formatIsoDate(value),
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-10 18:22:22 -04:00
|
|
|
function invoiceNumberLink(params: GridRenderCellParams<MockInvoice, string>) {
|
|
|
|
|
return (
|
|
|
|
|
<MuiLink
|
|
|
|
|
component={Link}
|
|
|
|
|
to="/invoices/:id"
|
|
|
|
|
params={{ id: params.row.id }}
|
|
|
|
|
underline="hover"
|
|
|
|
|
data-testid={`invoice-link-${params.row.id}`}
|
|
|
|
|
onClick={(event) => event.stopPropagation()}
|
|
|
|
|
>
|
|
|
|
|
{params.value}
|
|
|
|
|
</MuiLink>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 17:32:43 -04:00
|
|
|
export const pendingApprovalColumns: GridColDef<MockInvoice>[] = [
|
2026-08-10 18:22:22 -04:00
|
|
|
{
|
|
|
|
|
field: "invoiceNumbers",
|
|
|
|
|
headerName: "Invoice #",
|
|
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
renderCell: invoiceNumberLink,
|
|
|
|
|
},
|
2026-08-10 17:32:43 -04:00
|
|
|
{ field: "vendorName", headerName: "Vendor", flex: 1.4, minWidth: 160 },
|
|
|
|
|
dateColumn("dueDate", "Due Date"),
|
|
|
|
|
dateColumn("sendPaymentOn", "Send Payment On"),
|
|
|
|
|
{ field: "paymentMethod", headerName: "Payment Method", flex: 1, minWidth: 130 },
|
|
|
|
|
moneyColumn("amountDue", "Amount Due"),
|
|
|
|
|
moneyColumn("amount", "Amount"),
|
|
|
|
|
];
|
|
|
|
|
|
2026-08-10 19:06:14 -04:00
|
|
|
/** Invoice Processing — For My Attention columns (AP-26). */
|
|
|
|
|
export const invoiceAttentionColumns: GridColDef<MockInvoice>[] = [
|
|
|
|
|
{
|
|
|
|
|
field: "invoiceNumbers",
|
|
|
|
|
headerName: "Invoice",
|
|
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
renderCell: invoiceNumberLink,
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-08-11 15:20:30 -04:00
|
|
|
field: "actionLabel",
|
2026-08-10 19:06:14 -04:00
|
|
|
headerName: "Action",
|
|
|
|
|
flex: 1.2,
|
|
|
|
|
minWidth: 140,
|
|
|
|
|
},
|
2026-08-10 17:32:43 -04:00
|
|
|
{ field: "vendorName", headerName: "Vendor", flex: 1.4, minWidth: 160 },
|
2026-08-10 19:06:14 -04:00
|
|
|
dateColumn("dueDate", "Due Date"),
|
|
|
|
|
moneyColumn("amountDue", "Σ Sum"),
|
|
|
|
|
];
|
|
|
|
|
|
2026-08-11 15:20:30 -04:00
|
|
|
const iconColumn = (
|
|
|
|
|
field: "hasBolt" | "hasBell" | "hasEye",
|
|
|
|
|
headerName: string,
|
|
|
|
|
Icon: typeof BoltOutlinedIcon,
|
|
|
|
|
): GridColDef<MockInvoice> => ({
|
|
|
|
|
field,
|
|
|
|
|
headerName,
|
|
|
|
|
width: 56,
|
|
|
|
|
sortable: false,
|
|
|
|
|
filterable: false,
|
|
|
|
|
renderCell: (params) => (params.value ? <Icon fontSize="small" color="action" /> : null),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** Full catalog column defs keyed by AP-41 HTML column keys (AP-47). */
|
|
|
|
|
export const catalogColumnDefs: Record<InvoiceColumnKey, GridColDef<MockInvoice>> = {
|
|
|
|
|
bolt: iconColumn("hasBolt", "", BoltOutlinedIcon),
|
|
|
|
|
bell: iconColumn("hasBell", "", NotificationsNoneOutlinedIcon),
|
|
|
|
|
invoiceId: {
|
|
|
|
|
field: "invoiceNumbers",
|
|
|
|
|
headerName: "Invoice",
|
|
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
renderCell: invoiceNumberLink,
|
|
|
|
|
},
|
|
|
|
|
userStatus: { field: "actionLabel", headerName: "Action", flex: 1.2, minWidth: 140 },
|
|
|
|
|
vendor: { field: "vendorName", headerName: "Vendor", flex: 1.4, minWidth: 160 },
|
|
|
|
|
lastUpdate: dateColumn("lastUpdate", "Last Update"),
|
|
|
|
|
invoiceDate: dateColumn("invoiceDate", "Invoice Date"),
|
|
|
|
|
dueDate: dateColumn("dueDate", "Due Date"),
|
|
|
|
|
amount: moneyColumn("amountDue", "Sum"),
|
|
|
|
|
tagging: { field: "tagging", headerName: "Tagging", flex: 0.8, minWidth: 100 },
|
|
|
|
|
fs: { field: "fs", headerName: "F.S.", flex: 0.6, minWidth: 80 },
|
|
|
|
|
eye: iconColumn("hasEye", "", VisibilityOutlinedIcon),
|
|
|
|
|
description: { field: "description", headerName: "Description", flex: 1.4, minWidth: 160 },
|
|
|
|
|
type: { field: "invoiceType", headerName: "Invoice Type", flex: 1, minWidth: 120 },
|
|
|
|
|
location: { field: "location", headerName: "Location", flex: 1, minWidth: 120 },
|
|
|
|
|
paymentAccount: {
|
|
|
|
|
field: "paymentAccount",
|
|
|
|
|
headerName: "Payment Account",
|
|
|
|
|
flex: 1.2,
|
|
|
|
|
minWidth: 140,
|
|
|
|
|
},
|
|
|
|
|
paymentDate: dateColumn("payDate", "Payment Date"),
|
|
|
|
|
processingBegan: dateColumn("processingBegan", "Processing Began"),
|
|
|
|
|
relatedInvoice: {
|
|
|
|
|
field: "relatedInvoice",
|
|
|
|
|
headerName: "Related Invoice",
|
|
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
valueFormatter: (value: string | null) => value ?? "—",
|
|
|
|
|
},
|
|
|
|
|
rowCount: {
|
|
|
|
|
field: "rowCount",
|
|
|
|
|
headerName: "Row count",
|
|
|
|
|
width: 100,
|
|
|
|
|
type: "number",
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function columnsFromCatalogKeys(keys: InvoiceColumnKey[]): GridColDef<MockInvoice>[] {
|
|
|
|
|
return keys.map((key) => catalogColumnDefs[key]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 19:06:14 -04:00
|
|
|
export const readyToPayColumns: GridColDef<MockInvoice>[] = [
|
2026-08-10 18:22:22 -04:00
|
|
|
{
|
|
|
|
|
field: "invoiceNumber",
|
2026-08-10 19:06:14 -04:00
|
|
|
headerName: "Invoice",
|
2026-08-10 18:22:22 -04:00
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
renderCell: invoiceNumberLink,
|
|
|
|
|
},
|
2026-08-10 19:06:14 -04:00
|
|
|
{ field: "vendorName", headerName: "Vendor Name", flex: 1.4, minWidth: 160 },
|
|
|
|
|
{ field: "paymentMethod", headerName: "Payment Method", flex: 1, minWidth: 130 },
|
|
|
|
|
dateColumn("sendPaymentOn", "Send Payment On"),
|
|
|
|
|
dateColumn("dueDate", "Due Date"),
|
|
|
|
|
dateColumn("payDate", "Pay Date", true),
|
2026-08-10 17:32:43 -04:00
|
|
|
moneyColumn("amountDue", "Amount Due"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export const allInvoicesColumns: GridColDef<MockInvoice>[] = [
|
2026-08-10 18:22:22 -04:00
|
|
|
{
|
|
|
|
|
field: "invoiceNumber",
|
|
|
|
|
headerName: "Invoice #",
|
|
|
|
|
flex: 1,
|
|
|
|
|
minWidth: 120,
|
|
|
|
|
renderCell: invoiceNumberLink,
|
|
|
|
|
},
|
2026-08-10 17:32:43 -04:00
|
|
|
{ field: "vendorName", headerName: "Vendor", flex: 1.4, minWidth: 160 },
|
|
|
|
|
{ field: "status", headerName: "Status", flex: 1, minWidth: 140 },
|
|
|
|
|
dateColumn("dueDate", "Due Date"),
|
|
|
|
|
dateColumn("payDate", "Pay Date"),
|
|
|
|
|
{ field: "paymentMethod", headerName: "Payment Method", flex: 1, minWidth: 130 },
|
|
|
|
|
moneyColumn("amountDue", "Amount Due"),
|
|
|
|
|
moneyColumn("amount", "Amount"),
|
|
|
|
|
];
|