@get-asset/react
Profit & Loss Report
Styled multi-period profit and loss table over the reporting range — the provider's DatePicker-driven range unless dateFrom / dateTodetach it. The card header carries a period Select (Month / Quarter / Year) wired to the SDK's internal breakdown state — switching it re-fetches the report and the table re-renders with one column per period the range covers. Subtotal rows render bold; nested ledger rows are indented by depth. When the business has tags, the same searchable, grouped multi-select the transactions Tag column uses appears before the period Select — it cannot create new tag values. Checking tag values filters the report to amounts allocated to those tags (multiple tags are OR'd) and it hides itself when the business has none. The download button next to the period selector exports the same view as an XLSX file, using whatever query parameters the table is currently displaying — including the tag filter. Below the responsive breakpoint the table collapses to a single column with an arrow-picker period strip; above it, the period header sticks to the top of the page when scrolling. Pass insights (shown in the preview below) to render the AI insights panel inside the card beneath the table — it answers the listed questions from the insights endpoint automatically, using the parameters on screen, including the selected tags.
Built on the headless ProfitLossReport compound from @get-asset/sdk.
Preview
Loading preview…
Required scopes
The access token used by AssetProvider must include these scopes for this primitive to fetch data successfully:
report:readtag:readbusiness:read
API reference
Root props
| Prop | Type | Default | Description |
|---|---|---|---|
dateFrom | CalendarDateString | — | Inclusive start (YYYY-MM-DD) of a fixed reporting range. Detaches the report from the provider's shared range entirely; must be paired with dateTo (passing one without the other throws). Use periodDateRange to derive the pair from a month/quarter/year. Omit both to follow the provider's DatePicker-driven range. |
dateTo | CalendarDateString | — | Inclusive end (YYYY-MM-DD) of the fixed reporting range. Must be paired with dateFrom. |
defaultBreakdown | "month" | "quarter" | "year" | — | Initial value of the Select. Defaults to "year". The table renders one column per year, quarter, or month the range covers. Ignored when the range is exactly one calendar month or quarter — the Select is then locked to the matching option. |
records | ReportLine[] | — | Override fetched data with a single snapshot. |
tagOptions | ProfitLossTagOption[] | — | Override the fetched tag filter options ({ label, value }[], value in field:value format) — primarily for testing/storybook. Legacy — prefer tagFields. Pass [] to hide the searchable Select Tags control. |
tagFields | TagFilterField[] | — | Override the fetched tag fields ({ name, values }[]) — preferred over tagOptions for testing/storybook. Pass [] to hide the tag filter. |
state | "loading" | "error" | "empty" | — | Force a specific state for testing. |
insights | boolean | { questions?, ask? } | — | Render the AI insights panel inside the card, beneath the table. The panel is self-contained: pass `true` and it fetches answers from the profit & loss insights endpoint automatically, using the same parameters the report is showing. Alongside the starter questions, an “Ask something else” input lets people type their own — it runs through the same endpoint and returns the same structured answer. Optionally pass an object to override the starter `questions`, or to inject `ask` — a fetcher returning a canned answer — to mock answers in docs or tests without hitting the API (this preview uses it). |
basis | "accrual" | "modified_cash" | — | Report on a specific accounting basis. Omit to inherit the AssetProvider's basis, which itself falls back to the Business's country — Canadian Businesses report on Accrual, everyone else on Cash (see Accounting basis). |
basisToggle | boolean | — | Show the Cash/Accrual select in the report header. Off by default. Flipping it moves every component that inherits the provider's basis, so it can't leave a screen half cash and half accrual (see Accounting basis). |
onLedgerSelect | (ledgerId: string) => void | — | Called with a row's ledger id when the viewer picks it (click or keyboard). When set, ledger rows render as interactive links; group headings and calculated rows stay inert. |
className | string | — | Forwarded to the outer card element. |
States
Loading preview…
Loading preview…
Loading preview…
Loading preview…
Accounting basis
AssetProfitLossReport reports on one of two accounting bases: Accrual (accrual on the wire), which counts invoices and bills when they are issued, or Cash (modified_cash), which counts them when the money moves. Cashflow components take no basis — cashflow is a cash concept.
The basis is resolved per component, first match winning:
- the report's own
basisprop; - the provider's basis —
defaultBasisonAssetProvider, or whatever the toggle was last flipped to; - the Business's country — Canadian Businesses report on Accrual, everyone else on Cash.
Only the last step costs a request — a Business read, which is why every basis-aware component's scopes includes business:read — and it is serial: the report holds its own fetch until the Business answers. One request is shared across every basis-aware component on the screen, but pass defaultBasis on AssetProvider when you already know the basis and skip it entirely. If the lookup fails the report falls back to Cash and stays there — the Retry button re-runs the report, not the basis lookup.
basisToggle is off by default: no control renders in the header and the report reports on its resolved basis. Turn it on and flipping the control moves every basis-aware component under the provider with it, so the toggle can never leave a screen half cash and half accrual. A component pinned with its own basis prop is the exception — it keeps the basis you gave it and renders no toggle of its own, which is how you show both bases side by side on purpose.
The XLSX export sends the basis currently on screen, so a downloaded file always matches what the viewer was looking at.