Expense Management
A complete expenses surface: My and Team transactions tabs with Action Items, a searchable transactions table, receipt upload, and a full-screen transaction review dialog.
Part ofFinOps
A complete expenses surface: My and Team transactions tabs with Action Items, a searchable transactions table, receipt upload, and a full-screen transaction review dialog.
Quick Start
import { ExpenseManagementWidget } from '@tesouro/embedded-components-react';
export function ExpensesPage() {
return (
<ExpenseManagementWidget
baseUrl="https://api.example.com"
widgetToken="wt_live_abc123"
organizationId="org_xyz"
bankDisplayName="Example Bank"
/>
);
}The widget renders no page heading; your page supplies it.
Behaviour
- The Team tab appears when the token's scopes include org-wide expense read access, or when the signed-in user manages at least one direct report (their Team rows are scoped to those reports). When Team is available the widget opens on it.
- The settings entry point renders only when you pass
onSettingsClickand the user's scopes allow it. The widget never navigates itself. - Receipt upload uses the widget's own upload and mailbox address by default. If you supply
receiptUpload.onFileUpload, the built-in receipt-matching tracker doesn't run.
Props
Widget props
| Prop | Type | Default | Description |
|---|---|---|---|
bankDisplayName | string | undefined | Bank name shown in the attribution header. |
bankLogoSrc | string | undefined | URL for the bank logo in the attribution header. |
bankLogoAlt | string | undefined | Alt text for the bank logo. |
isBankingTaglineVisible | boolean | undefined | Shows the "Business Banking provided by" tagline beside the header. |
onSettingsClickEvent | () => void | undefined | Renders a scope-gated settings entry point in the header; navigate from this callback. |
receiptUpload | object | undefined | Overrides the receipt-upload control's props, such as onFileUpload / onAwaitMatching; see Receipt upload. |
Label overrides
| Prop | Type | Default | Description |
|---|---|---|---|
screenLabelsLabels | Partial<ExpensesScreenLabels> | English | Tab labels and other screen shell copy. |
headerLabelsLabels | Partial<ExpensesHeaderLabels> | English | Bank-attribution header copy. |
transactionsTableLabelsLabels |
| English | Transactions table column headers and empty/loading copy. |
filterBarLabelsLabels |
| English | Search/filter bar copy. |
actionItemsLabelsLabels | Partial<ActionItemsListLabels> | English | Action Items list copy. |
statusBadgeLabelsLabels | Partial<ExpenseStatusBadgeLabels> | English | Status pill copy, shared between the table and the review dialog. |
transactionDetailsLabelsLabels | Partial<TransactionDetailsLabels> | English | Review dialog copy. |
transactionDetailsCardLabelsLabels | Partial<AccordionItemCardLabels> | English | Copy for the accordion cards inside the review dialog. |
transactionDetailsActionsLabelsLabels |
| English | Copy for the review dialog's action buttons. |
fileViewerLabelsLabels | Partial<FileViewerLabels> | English | Copy for the receipt/attachment file viewer inside the review dialog. |
Examples
Bank attribution, settings entry point and custom upload
<ExpenseManagementWidget
bankDisplayName="Example Bank"
bankLogoSrc="/assets/bank-logo.svg"
isBankingTaglineVisible
onSettingsClick={() => router.push('/settings/expenses')}
receiptUpload={{
onFileUpload: (file) => myUploadHandler(file),
}}
/>Receipt upload
The "Upload receipts" button in the header opens a popover with a drag-and-drop file picker and a copyable forwarding email address. receiptUpload overrides its props; every field is optional and falls back to the widget's own upload, matching tracker and mailbox address.
| Field | Type | Description |
|---|---|---|
emailAddress | string | Forwarding email address shown with a copy button. |
onFileUpload | (file: File) => unknown | Promise<unknown> | Called once per selected file and awaited; a rejection counts as a failed upload. |
onAwaitMatching | (uploadedCount: number) => Promise<ReceiptMatchSummary> | Awaited after every file in a batch uploads. Resolve once matching finishes. |
isUploading | boolean | Host loading flag. While true (or while a batch runs) the popover shows a processing state and blocks new uploads. |
onFileUploadis called once per file, up to 10 files per batch. The widget shows a loading toast, then a success, partial or failure toast based on how many calls resolved.- With
onAwaitMatching, a fully successful batch keeps its toast open through receipt matching, then reports all matched, a partial count, or "still processing". If it rejects, the toast falls back to the plain upload success message. ReceiptMatchSummaryis{ total, matched, unmatched, unreadable, stillProcessing }, each anumbercount of the batch's receipts.stillProcessingcounts receipts that never settled in time; it is not a failure.
TypeScript
import type {
ExpenseManagementWidgetProps,
TransactionDetailsLabels,
TransactionDetailsActionsLabels,
AccordionItemCardLabels,
FileViewerLabels,
} from '@tesouro/embedded-components-react';Auth fields are inherited from WidgetProviderProps; see WidgetProvider for cascade rules.