import BoltOutlinedIcon from "@mui/icons-material/BoltOutlined"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import NotificationsNoneOutlinedIcon from "@mui/icons-material/NotificationsNoneOutlined"; 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 type { InvoiceColumnKey } from "@/dashboard/column-catalog"; import { formatRelativeFromIsoDate, formatSlashDate } from "@/dashboard/invoice-date-format"; import type { MockInvoice } from "@/mocks"; import { Link } from "@/router"; const dateTimeFormatter = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }); function formatAmountUsd(value: number | null | undefined): string { if (value == null) { return "—"; } return `${value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, })} USD`; } function formatIsoDate(value: string | null | undefined): string { return formatSlashDate(value); } function formatCurrency(value: number | null | undefined): string { if (value == null) { return "—"; } return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(value); } const moneyColumn = (field: keyof MockInvoice, headerName: string): GridColDef => ({ 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 => ({ field, headerName, flex: 1, minWidth: 130, editable, valueFormatter: (value: string | null | undefined) => formatIsoDate(value), }); function twoLineCell(primary: string, secondary: string, primaryBold = false) { return ( {primary} {secondary} ); } function invoiceNumberLink(params: GridRenderCellParams) { return ( event.stopPropagation()} sx={{ fontWeight: 700, color: "text.primary" }} > {params.value} ); } export const pendingApprovalColumns: GridColDef[] = [ { field: "invoiceNumbers", headerName: "Invoice #", flex: 1, minWidth: 120, renderCell: invoiceNumberLink, }, { 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"), ]; /** Invoice Processing — For My Attention columns (AP-26). */ export const invoiceAttentionColumns: GridColDef[] = [ { field: "invoiceNumbers", headerName: "Invoice", flex: 1, minWidth: 120, renderCell: invoiceNumberLink, }, { field: "actionLabel", headerName: "Action", flex: 1.2, minWidth: 140, }, { field: "vendorName", headerName: "Vendor", flex: 1.4, minWidth: 160 }, dateColumn("dueDate", "Due Date"), moneyColumn("amountDue", "Σ Sum"), ]; const iconColumn = ( field: "hasBolt" | "hasBell" | "hasEye", Icon: typeof BoltOutlinedIcon, ): GridColDef => ({ field, headerName: "", width: 56, sortable: false, filterable: false, renderHeader: () => , renderCell: (params) => (params.value ? : null), }); function dualDateColumn( field: "invoiceDate" | "dueDate", headerName: string, ): GridColDef { return { field, headerName, flex: 1, minWidth: 120, renderCell: (params) => twoLineCell( formatSlashDate(params.value as string), formatRelativeFromIsoDate(params.value as string), ), }; } /** Full catalog column defs keyed by AP-41 HTML column keys (AP-47). */ export const catalogColumnDefs: Record> = { 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, renderCell: (params) => twoLineCell(params.row.vendorName, params.row.vendorId, true), }, lastUpdate: { field: "lastUpdate", headerName: "Last Update", flex: 1.6, minWidth: 180, renderCell: (params) => { const stamp = dateTimeFormatter.format(new Date(params.row.lastUpdateAt)); return twoLineCell( params.row.lastUpdateMessage, `${stamp} ${params.row.lastUpdateActorName}`, ); }, }, invoiceDate: dualDateColumn("invoiceDate", "Invoice Date"), dueDate: dualDateColumn("dueDate", "Due Date"), amount: { field: "amountDue", headerName: "Σ Sum", flex: 1, minWidth: 120, type: "number", renderHeader: () => ( Σ Sum ), renderCell: (params) => ( {formatAmountUsd(params.row.amountDue)} ), }, tagging: { field: "tagging", headerName: "Tagging", flex: 0.8, minWidth: 100 }, fs: { field: "fs", headerName: "F.S.", flex: 0.6, minWidth: 80, renderCell: (params) => params.row.fs ? : null, }, 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[] { return keys.map((key) => catalogColumnDefs[key]); } export const readyToPayColumns: GridColDef[] = [ { field: "invoiceNumber", headerName: "Invoice", flex: 1, minWidth: 120, renderCell: invoiceNumberLink, }, { 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), moneyColumn("amountDue", "Amount Due"), ]; export const allInvoicesColumns: GridColDef[] = [ { field: "invoiceNumber", headerName: "Invoice #", flex: 1, minWidth: 120, renderCell: invoiceNumberLink, }, { 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"), ];