@mx-cartographer/experiences 9.4.13 → 9.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/core/types/Insights.d.ts +14 -14
  3. package/dist/core/types/InsightsInstanceSlotData.d.ts +17 -2
  4. package/dist/core/types/InsightsInstanceSlotData.es.js.map +1 -1
  5. package/dist/core/types/localization/InsightsFeedCopy.d.ts +13 -0
  6. package/dist/insights-v2/CommonUtils.d.ts +2 -0
  7. package/dist/insights-v2/CommonUtils.es.js +8 -3
  8. package/dist/insights-v2/CommonUtils.es.js.map +1 -1
  9. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/TransactionDetailsBlock.d.ts +13 -0
  10. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/TransactionDetailsBlock.es.js +24 -0
  11. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/TransactionDetailsBlock.es.js.map +1 -0
  12. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/index.d.ts +3 -0
  13. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/transactionDetailsActions.d.ts +3 -0
  14. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/transactionDetailsActions.es.js +18 -0
  15. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/transactionDetailsActions.es.js.map +1 -0
  16. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/useTransactionDetailsBlock.d.ts +31 -0
  17. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/useTransactionDetailsBlock.es.js +16 -0
  18. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/useTransactionDetailsBlock.es.js.map +1 -0
  19. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/useTransactionDetailsPrepare.d.ts +15 -0
  20. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/useTransactionDetailsPrepare.es.js +13 -0
  21. package/dist/insights-v2/actionBlocks/TransactionDetailsBlock/useTransactionDetailsPrepare.es.js.map +1 -0
  22. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionDrillDown.d.ts +22 -0
  23. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionDrillDown.es.js +108 -0
  24. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionDrillDown.es.js.map +1 -0
  25. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.d.ts +8 -0
  26. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js +89 -0
  27. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js.map +1 -0
  28. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/layout.d.ts +68 -0
  29. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/layout.es.js +118 -0
  30. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/layout.es.js.map +1 -0
  31. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/useTransactionHistory.d.ts +15 -0
  32. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/useTransactionHistory.es.js +20 -0
  33. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/useTransactionHistory.es.js.map +1 -0
  34. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/utils.d.ts +64 -0
  35. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/utils.es.js +67 -0
  36. package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/utils.es.js.map +1 -0
  37. package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/layout.d.ts +2 -2
  38. package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/layout.es.js +2 -2
  39. package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/layout.es.js.map +1 -1
  40. package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/utils.d.ts +14 -3
  41. package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/utils.es.js +26 -25
  42. package/dist/insights-v2/blocks/ChartBlocks/BarchartBlock/utils.es.js.map +1 -1
  43. package/dist/insights-v2/blocks/ChartBlocks/DonutChartBlock/DonutChartBlock.es.js +3 -3
  44. package/dist/insights-v2/blocks/ChartBlocks/DonutChartBlock/DonutChartBlock.es.js.map +1 -1
  45. package/dist/insights-v2/mappings/blocks.es.js +48 -34
  46. package/dist/insights-v2/mappings/blocks.es.js.map +1 -1
  47. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## [9.4.15] - 09-03-2026
2
+
3
+ - **ADDED** - Transaction History action block (single-bar): `transaction_history_block` registered in `mappings/blocks.ts`, rendering a `BarChartV2` over the beat's monthly `data_series` above a reused transactions `TransactionList` drilldown. Variant behavior (chart top, bar colors, drawer header, and the per-variant `filterStrategy`) is driven by `drillDownConfig`, covering the Transaction, CategorySpend, MonthlySpend, AccountBalance, and UpcomingBill variants (WEC-1701)
4
+ - **ADDED** - Upcoming-bill drilldown synthesizes its chart bars from the beat's recurring-bill transactions plus a projected bill read off `instance_slot_data` (`buildUpcomingBillSeries`), de-duped by date and sorted oldest→newest with a `TRN-projected-bill` sentinel bar (WEC-1701)
5
+ - **CHANGED** - Migrated insights-v2 instance-slot block names and bar-chart variant names to snake_case to match the backend contract: `BlockType` values, the `mappings/blocks.ts` map keys, `BAR_CHART_VARIANTS`, `BarchartBlock` layout keys, and the corresponding mock payloads (e.g. `bar_chart_block`, `category_color`, `monthly_spend_comparison`, `savings_account_balances_summary`, `budget_block`, `category_transactions_block`) (WEC-1701)
6
+ - **CHANGED** - `buildChartData` accepts an optional date format (defaults to month-short; upcoming-bill passes month-day) and now carries each bar's `date` so chart tabs, filter indexing, and the drawer header stay aligned (WEC-1701)
7
+ - **CHANGED** - `Legends` type made consistent: `additional_value`, `additional_value_type`, and `value_type` are now optional, and `instance_slot_data.legends` is typed as `Legends[]` (WEC-1701)
8
+ - **CHANGED** - `drillDownConfig` type-hardened: `DataSeriesItem` replaces `any[]` for the data series shape; removed the unused `filterKind` field; `filterStrategy` is now a required discriminated union returning a typed `TransactionFilter` or `{ skip: true }` (WEC-1701)
9
+ - **CHANGED** - `ActionDrawerState.context` stays `unknown` at the store level; drilldown components narrow to their expected payload shape via the new `useActionContext<DrilldownPayload>()` hook (WEC-1701)
10
+ - **CHANGED** - `TransactionHistoryBlock` snapshots the shared transaction filter on mount and restores it on unmount via a `useLayoutEffect` cleanup function, keeping beat-specific filter state scoped to the block lifecycle instead of leaking into `InsightsV2Store` as a global concern (WEC-1701)
11
+ - **FIXED** - Transaction History block renders the shared `EmptyBarChart` empty state instead of crashing when a single-bar or upcoming-bill beat produces no chart data (WEC-1701)
12
+ - **FIXED** - Upcoming-bill drilldown rendered an empty chart because the recurring-bill scoping guids were read from the top-level `Beat`; they (plus `primary_transaction_guid` and `account_guids`) are now sourced from `instance_slot_data`, where the payload actually carries them, so the synthesized bars render again (WEC-1701)
13
+ - **FIXED** - `TransactionHistoryBlock` `useLayoutEffect` deps now use stable joined-string keys (`beatTransactionGuidsKey`, `recurringBillGuidsKey`) instead of unmemoized computed arrays, preventing infinite render/fetch loops (WEC-1701)
14
+ - **ADDED** - Unit tests for the Transaction History action block: `drillDownConfig` + filter strategies, `utils` (flattened series, upcoming-bill synthesis, drawer titles, default selection), the `useTransactionHistory` hook, `TransactionDrillDown`, the block render, `buildChartData` date-format behavior, and the block registry mapping (WEC-1701)
15
+
16
+ ## [9.4.14] - 09-03-2026
17
+
18
+ - **ADDED** - insights-v2 `TransactionDetailsBlock` action block: shared drawer content any transaction beat opens via `primary_cta.block = 'TransactionDetailsBlock'`. Resolves its subject transaction from `instance_slot_data.transaction_guid`, then `beat.primary_transaction_guid`, then `transaction_guids[0]`, preferring the `transactionStore` object over the payload snapshot so inline edits write through; maps backend action-name strings to the `TopActions`/`BottomActions` enums (unknown names dropped, `undefined` means defaults, `[]` means none); `useTransactionDetailsPrepare` loads transaction data at drawer-open time. Registered in `mappings/blocks.ts`, with a Storybook story (WEC-1499)
19
+ - **ADDED** - `TransactionDetailsBlockData` to the insights-v2 instance-slot-data union (WEC-1499)
20
+ - **FIXED** - `TransactionDetailsBlock` no longer falls back to `beat.primary_transaction` when it is a snapshot of a *different* transaction than the resolved guid — previously a beat naming its subject via `instance_slot_data` whose guid missed in the store rendered the wrong transaction's details, and inline edits were made against the wrong subject (WEC-1499)
21
+
1
22
  ## [9.4.13] - 09-02-2026
2
23
 
3
24
  - **CHANGED** - the theme shape radius is half of `card_corner_radius`, so cards render at 2x the theme radius per MXUI convention (MEW-3445)
@@ -26,9 +26,9 @@ export interface InsightsPayload {
26
26
  export interface Legends {
27
27
  label: string;
28
28
  value: number;
29
- additional_value: number;
30
- additional_value_type: string;
31
- value_type: string;
29
+ additional_value?: number;
30
+ additional_value_type?: string;
31
+ value_type?: string;
32
32
  }
33
33
  export interface Logo {
34
34
  category_guid?: string;
@@ -75,18 +75,18 @@ export interface Footer {
75
75
  account: string;
76
76
  }
77
77
  export declare const BlockType: {
78
- readonly AccountBlock: "AccountBlock";
79
- readonly BarChartBlock: "BarChartBlock";
80
- readonly BudgetBlock: "BudgetBlock";
81
- readonly CategoryTransactionsBlock: "CategoryTransactionsBlock";
82
- readonly ImageBlock: "ImageBlock";
83
- readonly DonutChartBlock: "DonutChartBlock";
84
- readonly DoubleBarChartBlock: "DoubleBarChartBlock";
85
- readonly DoubleProgressBarBlock: "DoubleProgressBarBlock";
86
- readonly StepChartBlock: "StepChartBlock";
87
- readonly SVGImageBlock: "SVGImageBlock";
78
+ readonly AccountBlock: "account_block";
79
+ readonly BarChartBlock: "bar_chart_block";
80
+ readonly BudgetBlock: "budget_block";
81
+ readonly CategoryTransactionsBlock: "category_transactions_block";
82
+ readonly ImageBlock: "image_block";
83
+ readonly DonutChartBlock: "donut_chart_block";
84
+ readonly DoubleBarChartBlock: "double_bar_chart_block";
85
+ readonly DoubleProgressBarBlock: "double_progress_bar_block";
86
+ readonly StepChartBlock: "step_chart_block";
87
+ readonly SVGImageBlock: "svg_image_block";
88
88
  };
89
- export type BlockType = keyof typeof BlockType;
89
+ export type BlockType = (typeof BlockType)[keyof typeof BlockType];
90
90
  export interface InstanceSlot {
91
91
  block: BlockType;
92
92
  variant: string | null;
@@ -1,12 +1,16 @@
1
1
  import { DataSeries, Legends } from '../../core';
2
2
  import { Transaction } from './Transaction';
3
- export interface InsightsInstanceSlotData extends Partial<BarChartBlockData>, Partial<BudgetBlockData>, Partial<DonutChartBlockData>, Partial<DoubleProgressBarBlockData>, Partial<ImageBlockData>, Partial<AccountBlockData>, Partial<SaveExtraBlockData>, Partial<StepChartBlockData>, Partial<SVGImageBlockData>, Partial<CategoryTransactionsBlockData>, Partial<AccountSwitchBlockData>, Partial<CommonProps> {
3
+ export interface InsightsInstanceSlotData extends Partial<BarChartBlockData>, Partial<BudgetBlockData>, Partial<DonutChartBlockData>, Partial<DoubleProgressBarBlockData>, Partial<ImageBlockData>, Partial<AccountBlockData>, Partial<SaveExtraBlockData>, Partial<StepChartBlockData>, Partial<SVGImageBlockData>, Partial<CategoryTransactionsBlockData>, Partial<AccountSwitchBlockData>, Partial<TransactionDetailsBlockData>, Partial<CommonProps> {
4
4
  }
5
5
  interface CommonProps {
6
6
  amount?: number;
7
- legends?: (Legends | LegendProps)[];
7
+ date?: string;
8
+ legends?: Legends[];
8
9
  category_guid?: string;
9
10
  data_series?: DataSeries | DataSet[];
11
+ transaction_guids?: string[];
12
+ account_guids?: string[];
13
+ primary_transaction_guid?: string;
10
14
  }
11
15
  export interface BarChartBlockData {
12
16
  average_amount?: number;
@@ -122,4 +126,15 @@ export type InsightTransaction = Pick<Transaction, 'amount'> & Partial<Pick<Tran
122
126
  export interface CategoryTransactionsBlockData {
123
127
  primary_transaction?: InsightTransaction;
124
128
  }
129
+ export interface TransactionDetailsBlockData {
130
+ /**
131
+ * Explicit subject of the details drawer. Optional: the block falls back to
132
+ * `beat.primary_transaction_guid`, then `beat.transaction_guids[0]`.
133
+ */
134
+ transaction_guid?: string;
135
+ /** `TopActions` member names, e.g. `['Category', 'Date', 'Memo']`. Omit for the defaults. */
136
+ top_actions?: string[];
137
+ /** `BottomActions` member names, e.g. `['MerchantBudget', 'HideTransaction']`. Omit for defaults. */
138
+ bottom_actions?: string[];
139
+ }
125
140
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"InsightsInstanceSlotData.es.js","names":[],"sources":["../../../src/core/types/InsightsInstanceSlotData.ts"],"sourcesContent":["import type { DataSeries, Legends } from '../../core'\nimport type { Transaction } from './Transaction'\n\nexport interface InsightsInstanceSlotData\n extends\n Partial<BarChartBlockData>,\n Partial<BudgetBlockData>,\n Partial<DonutChartBlockData>,\n Partial<DoubleProgressBarBlockData>,\n Partial<ImageBlockData>,\n Partial<AccountBlockData>,\n Partial<SaveExtraBlockData>,\n Partial<StepChartBlockData>,\n Partial<SVGImageBlockData>,\n Partial<CategoryTransactionsBlockData>,\n Partial<AccountSwitchBlockData>,\n Partial<CommonProps> {}\n\n//when any of the type is used more than one times, we can use the common props\ninterface CommonProps {\n amount?: number\n legends?: (Legends | LegendProps)[]\n category_guid?: string\n data_series?: DataSeries | DataSet[]\n}\n\n//BarChart Block\nexport interface BarChartBlockData {\n average_amount?: number\n ariaLabel?: string\n}\n\n//DonutChart Block\ninterface Main {\n value: string\n value_type: string\n label: string\n}\nexport interface DonutChartBlockData {\n main: Main\n}\n\n//DoubleBarChart Block\n// TODO : This has to be revisited again to update the below types based on API response once we have the final shape of the data_series for double bar chart from BE\nexport interface LegendProps extends Legends {\n amount: number\n formattedAmount: string\n label: string\n color?: string\n}\n\nexport interface DataSet extends DataSeries {\n ariaLabelComparison: string\n ariaLabelMain: string\n comparison: number\n formattedComparison: string\n formattedMain: string\n label: string\n main: number\n}\n\n//DoubleProgressBar Block\n\ninterface DoubleProgressBarProps {\n label: string\n value: number\n}\nexport interface DoubleProgressBarBlockData {\n doubleProgressBar: {\n previous: DoubleProgressBarProps\n latest: DoubleProgressBarProps\n }\n}\n\n//Image Block\nexport interface ImageBlockData {\n image: string\n}\n\n//Account Block\nexport interface AccountBlockData {\n sub_text: string\n institution_guid?: string\n institution_name?: string\n icon_name?: string\n /** Header bar text (standard card), or the colored category strip label (deposit cards). */\n header_text?: string\n // Deposit variants (Unified Deposit, Unified Deposit Emergency Fund, Savings Account Deposit)\n /** Merchant guid — renders a real merchant logo in the deposit card when present. */\n merchant_guid?: string\n /** Merchant display name — logo alt text and the payer line under the amount. */\n merchant_name?: string\n /** Secondary label beside the amount, e.g. `On {date}, you saved` or `Balance as of MM/DD/YY`. */\n label?: string\n}\n\nexport interface SaveExtraBlockData {\n /** Styled segments `[lead-in, amount digits, trailing copy]`, e.g. `['Save an extra', '100', 'to help your funds grow']`. */\n primary_text?: string[]\n /** Styled segments `[projected balance, caption]`, e.g. `['$8,313.87', 'After payday & projected expenses']`. */\n secondary_text?: string[]\n /** MakeTransfer drilldown: available balance (both variants). */\n available_balance?: number\n /** SAVE_ENOUGH_TO_LIVE_ON: average monthly spend. */\n avg_monthly_spend?: number\n /** SAVE_ENOUGH_TO_LIVE_ON: how long the balance lasts, overlaid on the timeline SVG (e.g. \"2 months, 11 days\"). */\n time_period?: string\n /** SAVE_AN_EXTRA_100: upcoming expenses (the \"Expenses\" figure). */\n upcoming_expenses?: number\n /** SAVE_AN_EXTRA_100: total remaining after expenses. */\n total_remaining?: number\n /** SAVE_ENOUGH_TO_LIVE_ON: date for the \"Your available balances as of {date}\" line. */\n date?: string\n}\n\n//AccountSwitch Block\nexport interface AccountSwitchBlockData {\n discovered_institution_guid: string\n discovered_institution_name: string\n held_institution_guid: string\n held_institution_name: string\n is_connected: boolean\n}\n\n// StepChart Block\nexport interface StepChartBlockData {\n completed_steps: number\n label_1: string\n label_2?: string\n}\n\n//Budgets Blocks\nexport enum BudgetTierType {\n LOW_BUDGET_TIER = 'LOW',\n MEDIUM_BUDGET_TIER = 'MEDIUM',\n OFF_TRACK_BUDGET_TIER = 'OFF_TRACK',\n}\n\nexport type BudgetTierValue = `${BudgetTierType}`\n\nexport interface BudgetLocalizedPayload {\n primary_text?: string\n amount_left_label?: string\n spent_label?: string\n budgeted_label?: string\n}\n\nexport interface BudgetBlockData {\n budget_percent: number\n budget_tier: BudgetTierValue\n category_guid?: string\n merchant_guid?: string\n spent_amount: number\n budgeted_amount: number\n localized_payload: BudgetLocalizedPayload\n}\n\nexport interface SVGImageBlockData {\n isCIBC?: boolean\n}\n\nexport type InsightTransaction = Pick<Transaction, 'amount'> &\n Partial<Pick<Transaction, 'category_guid' | 'date' | 'description' | 'merchant_guid'>> & {\n merchant_name?: string\n }\n\nexport interface CategoryTransactionsBlockData {\n primary_transaction?: InsightTransaction\n}\n"],"mappings":"AAoIA,IAAY,IAAL,0BAAA,GAAA;AACL,SAAA,EAAA,kBAAA,OACA,EAAA,qBAAA,UACA,EAAA,wBAAA;AACF,GAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"InsightsInstanceSlotData.es.js","names":[],"sources":["../../../src/core/types/InsightsInstanceSlotData.ts"],"sourcesContent":["import type { DataSeries, Legends } from '../../core'\nimport type { Transaction } from './Transaction'\n\nexport interface InsightsInstanceSlotData\n extends\n Partial<BarChartBlockData>,\n Partial<BudgetBlockData>,\n Partial<DonutChartBlockData>,\n Partial<DoubleProgressBarBlockData>,\n Partial<ImageBlockData>,\n Partial<AccountBlockData>,\n Partial<SaveExtraBlockData>,\n Partial<StepChartBlockData>,\n Partial<SVGImageBlockData>,\n Partial<CategoryTransactionsBlockData>,\n Partial<AccountSwitchBlockData>,\n Partial<TransactionDetailsBlockData>,\n Partial<CommonProps> {}\n\n//when any of the type is used more than one times, we can use the common props\ninterface CommonProps {\n amount?: number\n date?: string\n legends?: Legends[]\n category_guid?: string\n data_series?: DataSeries | DataSet[]\n transaction_guids?: string[]\n account_guids?: string[]\n primary_transaction_guid?: string\n}\n\n//BarChart Block\nexport interface BarChartBlockData {\n average_amount?: number\n ariaLabel?: string\n}\n\n//DonutChart Block\ninterface Main {\n value: string\n value_type: string\n label: string\n}\nexport interface DonutChartBlockData {\n main: Main\n}\n\n//DoubleBarChart Block\n// TODO : This has to be revisited again to update the below types based on API response once we have the final shape of the data_series for double bar chart from BE\nexport interface LegendProps extends Legends {\n amount: number\n formattedAmount: string\n label: string\n color?: string\n}\n\nexport interface DataSet extends DataSeries {\n ariaLabelComparison: string\n ariaLabelMain: string\n comparison: number\n formattedComparison: string\n formattedMain: string\n label: string\n main: number\n}\n\n//DoubleProgressBar Block\n\ninterface DoubleProgressBarProps {\n label: string\n value: number\n}\nexport interface DoubleProgressBarBlockData {\n doubleProgressBar: {\n previous: DoubleProgressBarProps\n latest: DoubleProgressBarProps\n }\n}\n\n//Image Block\nexport interface ImageBlockData {\n image: string\n}\n\n//Account Block\nexport interface AccountBlockData {\n sub_text: string\n institution_guid?: string\n institution_name?: string\n icon_name?: string\n /** Header bar text (standard card), or the colored category strip label (deposit cards). */\n header_text?: string\n // Deposit variants (Unified Deposit, Unified Deposit Emergency Fund, Savings Account Deposit)\n /** Merchant guid — renders a real merchant logo in the deposit card when present. */\n merchant_guid?: string\n /** Merchant display name — logo alt text and the payer line under the amount. */\n merchant_name?: string\n /** Secondary label beside the amount, e.g. `On {date}, you saved` or `Balance as of MM/DD/YY`. */\n label?: string\n}\n\nexport interface SaveExtraBlockData {\n /** Styled segments `[lead-in, amount digits, trailing copy]`, e.g. `['Save an extra', '100', 'to help your funds grow']`. */\n primary_text?: string[]\n /** Styled segments `[projected balance, caption]`, e.g. `['$8,313.87', 'After payday & projected expenses']`. */\n secondary_text?: string[]\n /** MakeTransfer drilldown: available balance (both variants). */\n available_balance?: number\n /** SAVE_ENOUGH_TO_LIVE_ON: average monthly spend. */\n avg_monthly_spend?: number\n /** SAVE_ENOUGH_TO_LIVE_ON: how long the balance lasts, overlaid on the timeline SVG (e.g. \"2 months, 11 days\"). */\n time_period?: string\n /** SAVE_AN_EXTRA_100: upcoming expenses (the \"Expenses\" figure). */\n upcoming_expenses?: number\n /** SAVE_AN_EXTRA_100: total remaining after expenses. */\n total_remaining?: number\n /** SAVE_ENOUGH_TO_LIVE_ON: date for the \"Your available balances as of {date}\" line. */\n date?: string\n}\n\n//AccountSwitch Block\nexport interface AccountSwitchBlockData {\n discovered_institution_guid: string\n discovered_institution_name: string\n held_institution_guid: string\n held_institution_name: string\n is_connected: boolean\n}\n\n// StepChart Block\nexport interface StepChartBlockData {\n completed_steps: number\n label_1: string\n label_2?: string\n}\n\n//Budgets Blocks\nexport enum BudgetTierType {\n LOW_BUDGET_TIER = 'LOW',\n MEDIUM_BUDGET_TIER = 'MEDIUM',\n OFF_TRACK_BUDGET_TIER = 'OFF_TRACK',\n}\n\nexport type BudgetTierValue = `${BudgetTierType}`\n\nexport interface BudgetLocalizedPayload {\n primary_text?: string\n amount_left_label?: string\n spent_label?: string\n budgeted_label?: string\n}\n\nexport interface BudgetBlockData {\n budget_percent: number\n budget_tier: BudgetTierValue\n category_guid?: string\n merchant_guid?: string\n spent_amount: number\n budgeted_amount: number\n localized_payload: BudgetLocalizedPayload\n}\n\nexport interface SVGImageBlockData {\n isCIBC?: boolean\n}\n\nexport type InsightTransaction = Pick<Transaction, 'amount'> &\n Partial<Pick<Transaction, 'category_guid' | 'date' | 'description' | 'merchant_guid'>> & {\n merchant_name?: string\n }\n\nexport interface CategoryTransactionsBlockData {\n primary_transaction?: InsightTransaction\n}\n\n// TransactionDetails Action Block\nexport interface TransactionDetailsBlockData {\n /**\n * Explicit subject of the details drawer. Optional: the block falls back to\n * `beat.primary_transaction_guid`, then `beat.transaction_guids[0]`.\n */\n transaction_guid?: string\n /** `TopActions` member names, e.g. `['Category', 'Date', 'Memo']`. Omit for the defaults. */\n top_actions?: string[]\n /** `BottomActions` member names, e.g. `['MerchantBudget', 'HideTransaction']`. Omit for defaults. */\n bottom_actions?: string[]\n}\n"],"mappings":"AAyIA,IAAY,IAAL,0BAAA,GAAA;AACL,SAAA,EAAA,kBAAA,OACA,EAAA,qBAAA,UACA,EAAA,wBAAA;AACF,GAAA,CAAA,CAAA"}
@@ -48,9 +48,22 @@ export interface MakeTransferCopy {
48
48
  increment_aria_label: string;
49
49
  decrement_aria_label: string;
50
50
  }
51
+ export interface TransactionHistoryBlock {
52
+ category_spend_drawer_title: string;
53
+ monthly_spend_comparison_drawer_title: string;
54
+ savings_account_balances_summary_drawer_title: string;
55
+ upcoming_bill_drawer_title: string;
56
+ monthly_spend_to_income_drawer_title: string;
57
+ monthly_debt_to_income_drawer_title: string;
58
+ monthly_savings_to_income_drawer_title: string;
59
+ cost_living_to_income_drawer_title: string;
60
+ non_essential_category_summary_drawer_title: string;
61
+ monthly_deposit_summary_drawer_title: string;
62
+ }
51
63
  export interface InsightsFeedCopy {
52
64
  general: GeneralInsightsFeedCopy;
53
65
  settings_menu: SettingsMenuCopy;
54
66
  make_transfer: MakeTransferCopy;
67
+ transaction_history_block: TransactionHistoryBlock;
55
68
  }
56
69
  export {};
@@ -1 +1,3 @@
1
+ import { Transaction } from '../core';
1
2
  export declare const formatValueByType: (value: number, type: string, format: string) => string;
3
+ export declare const selectTransactionsByGuids: (detailedTransactions: Transaction[], guids: string[] | undefined) => Transaction[];
@@ -1,7 +1,12 @@
1
- import { formatCurrency as e } from "../core/utils/NumberFormatting.es.js";
2
- var f = (r, t, n) => t === "amount" ? e(r, n) : t === "percent" ? `${r}%` : String(r);
1
+ import { formatCurrency as i } from "../core/utils/NumberFormatting.es.js";
2
+ var u = (r, t, e) => t === "amount" ? i(r, e) : t === "percent" ? `${r}%` : String(r), a = (r, t) => {
3
+ if (!t?.length) return [];
4
+ const e = new Set(t);
5
+ return r.filter((n) => e.has(n.guid));
6
+ };
3
7
  export {
4
- f as formatValueByType
8
+ u as formatValueByType,
9
+ a as selectTransactionsByGuids
5
10
  };
6
11
 
7
12
  //# sourceMappingURL=CommonUtils.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommonUtils.es.js","names":[],"sources":["../../src/insights-v2/CommonUtils.ts"],"sourcesContent":["import { formatCurrency } from '../core/utils/NumberFormatting'\n\nexport const formatValueByType = (value: number, type: string, format: string): string => {\n if (type === 'amount') return formatCurrency(value, format)\n if (type === 'percent') return `${value}%`\n return String(value)\n}\n"],"mappings":";AAEA,IAAa,IAAA,CAAqB,GAAe,GAAc,MACzD,MAAS,WAAiB,EAAe,GAAO,CAAM,IACtD,MAAS,YAAkB,GAAG,CAAA,MAC3B,OAAO,CAAK"}
1
+ {"version":3,"file":"CommonUtils.es.js","names":[],"sources":["../../src/insights-v2/CommonUtils.ts"],"sourcesContent":["import type { Transaction } from '../core'\nimport { formatCurrency } from '../core/utils/NumberFormatting'\n\nexport const formatValueByType = (value: number, type: string, format: string): string => {\n if (type === 'amount') return formatCurrency(value, format)\n if (type === 'percent') return `${value}%`\n return String(value)\n}\n\nexport const selectTransactionsByGuids = (\n detailedTransactions: Transaction[],\n guids: string[] | undefined,\n): Transaction[] => {\n if (!guids?.length) return []\n const guidSet = new Set(guids)\n return detailedTransactions.filter((transaction) => guidSet.has(transaction.guid))\n}\n"],"mappings":";AAGA,IAAa,IAAA,CAAqB,GAAe,GAAc,MACzD,MAAS,WAAiB,EAAe,GAAO,CAAM,IACtD,MAAS,YAAkB,GAAG,CAAA,MAC3B,OAAO,CAAK,GAGR,IAAA,CACX,GACA,MACkB;AAClB,MAAI,CAAC,GAAO,OAAQ,QAAO,CAAC;AAC5B,QAAM,IAAU,IAAI,IAAI,CAAK;AAC7B,SAAO,EAAqB,OAAA,CAAQ,MAAgB,EAAQ,IAAI,EAAY,IAAI,CAAC;AACnF"}
@@ -0,0 +1,13 @@
1
+ import { ActionBlockProps } from '../../mappings/blocks';
2
+ /**
3
+ * Single-transaction drawer content. The detail rendering and inline editing (category picker, date,
4
+ * memo, tags, split/hide/merchant-budget) all live in the shared `TransactionDetails` component from
5
+ * the transactions module — this block's job is only to resolve *which* transaction and *which*
6
+ * actions, then hand off.
7
+ *
8
+ * `observer` is required: the resolved transaction comes from TransactionStore, and `usePrepare` may
9
+ * still be loading it when this first renders. Without it the drawer would stay on the loader.
10
+ */
11
+ export declare const TransactionDetailsBlock: (({ beat, }: ActionBlockProps) => import("react/jsx-runtime").JSX.Element) & {
12
+ displayName: string;
13
+ };
@@ -0,0 +1,24 @@
1
+ import { useGlobalCopyStore as e } from "../../../common/context/hooks.es.js";
2
+ import s from "../../../common/components/Loader.es.js";
3
+ import c from "../../../transactions/components/shared/TransactionDetails.es.js";
4
+ import { useTransactionDetailsBlock as p } from "./useTransactionDetailsBlock.es.js";
5
+ import { observer as f } from "mobx-react-lite";
6
+ import l from "@mui/material/Stack";
7
+ import { Text as u } from "@mxenabled/mxui";
8
+ import { jsx as o } from "react/jsx-runtime";
9
+ var v = f(function({ beat: t }) {
10
+ const { common: i } = e(), { bottomActions: n, isLoading: m, topActions: a, transaction: r } = p(t);
11
+ return m ? /* @__PURE__ */ o(s, {}) : r ? /* @__PURE__ */ o(c, {
12
+ bottomActions: n,
13
+ topActions: a,
14
+ transaction: r
15
+ }) : /* @__PURE__ */ o(l, {
16
+ sx: { p: 24 },
17
+ children: /* @__PURE__ */ o(u, { children: i.error_message })
18
+ });
19
+ });
20
+ export {
21
+ v as TransactionDetailsBlock
22
+ };
23
+
24
+ //# sourceMappingURL=TransactionDetailsBlock.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransactionDetailsBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionDetailsBlock/TransactionDetailsBlock.tsx"],"sourcesContent":["import Stack from '@mui/material/Stack'\n\nimport { Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport { Loader, useGlobalCopyStore } from '../../../common'\nimport TransactionDetails from '../../../transactions/components/shared/TransactionDetails'\nimport type { ActionBlockProps } from '../../mappings/blocks'\n\nimport { useTransactionDetailsBlock } from './useTransactionDetailsBlock'\n\n/**\n * Single-transaction drawer content. The detail rendering and inline editing (category picker, date,\n * memo, tags, split/hide/merchant-budget) all live in the shared `TransactionDetails` component from\n * the transactions module — this block's job is only to resolve *which* transaction and *which*\n * actions, then hand off.\n *\n * `observer` is required: the resolved transaction comes from TransactionStore, and `usePrepare` may\n * still be loading it when this first renders. Without it the drawer would stay on the loader.\n */\nexport const TransactionDetailsBlock = observer(function TransactionDetailsBlock({\n beat,\n}: ActionBlockProps) {\n const { common: commonCopy } = useGlobalCopyStore()\n const { bottomActions, isLoading, topActions, transaction } = useTransactionDetailsBlock(beat)\n\n if (isLoading) return <Loader />\n\n // Loading finished and the guid still resolves to nothing — a stale beat pointing at a deleted or\n // out-of-window transaction. Mirrors ActionBlockLayer's unknown-block branch rather than rendering\n // an empty drawer.\n if (!transaction) {\n return (\n <Stack sx={{ p: 24 }}>\n <Text>{commonCopy.error_message}</Text>\n </Stack>\n )\n }\n\n return (\n <TransactionDetails\n bottomActions={bottomActions}\n topActions={topActions}\n transaction={transaction}\n />\n )\n})\n"],"mappings":";;;;;;;;AAoBA,IAAa,IAA0B,EAAS,SAAiC,EAC/E,MAAA,EAAA,GACmB;AACnB,QAAM,EAAE,QAAQ,EAAA,IAAe,EAAmB,GAC5C,EAAE,eAAA,GAAe,WAAA,GAAW,YAAA,GAAY,aAAA,EAAA,IAAgB,EAA2B,CAAI;AAE7F,SAAI,IAAkB,gBAAA,EAAC,GAAD,CAAS,CAAA,IAK1B,IASH,gBAAA,EAAC,GAAD;AAAA,IACiB,eAAA;AAAA,IACH,YAAA;AAAA,IACC,aAAA;AAAA,EACd,CAAA,IAXC,gBAAA,EAAC,GAAD;AAAA,IAAO,IAAI,EAAE,GAAG,GAAG;AAAA,IACjB,UAAA,gBAAA,EAAC,GAAD,EAAA,UAAO,EAAW,cAAoB,CAAA;AAAA,EACjC,CAAA;AAWb,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './TransactionDetailsBlock';
2
+ export * from './useTransactionDetailsBlock';
3
+ export * from './useTransactionDetailsPrepare';
@@ -0,0 +1,3 @@
1
+ import { BottomActions, TopActions } from '../../../transactions/constants/Actions';
2
+ export declare const resolveTopActions: (names?: string[]) => TopActions[];
3
+ export declare const resolveBottomActions: (names?: string[]) => BottomActions[];
@@ -0,0 +1,18 @@
1
+ import { BottomActions as t, DEFAULT_BOTTOM_ACTIONS as i, DEFAULT_TOP_ACTIONS as c, TopActions as r } from "../../../transactions/constants/Actions.es.js";
2
+ var s = {
3
+ Category: r.Category,
4
+ Date: r.Date,
5
+ Memo: r.Memo,
6
+ Tags: r.Tags
7
+ }, A = {
8
+ MerchantBudget: t.MerchantBudget,
9
+ SplitTransaction: t.SplitTransaction,
10
+ HideTransaction: t.HideTransaction,
11
+ MerchantLogo: t.MerchantLogo
12
+ }, e = (o, T, n) => o ? o.map((a) => T[a]).filter((a) => a !== void 0) : n, O = (o) => e(o, s, c), _ = (o) => e(o, A, i);
13
+ export {
14
+ _ as resolveBottomActions,
15
+ O as resolveTopActions
16
+ };
17
+
18
+ //# sourceMappingURL=transactionDetailsActions.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactionDetailsActions.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionDetailsBlock/transactionDetailsActions.ts"],"sourcesContent":["import {\n BottomActions,\n DEFAULT_BOTTOM_ACTIONS,\n DEFAULT_TOP_ACTIONS,\n TopActions,\n} from '../../../transactions/constants/Actions'\n\n/**\n * The backend sends action names as strings on `instance_slot_data`; `TransactionDetails` takes the\n * numeric enums. Keep the lookup keyed by the enum's own member names so adding a member to\n * `Actions.ts` is picked up here without a second edit.\n *\n * Unknown names are dropped rather than throwing: a beat configured against a newer ditto config\n * than the bundle should render with the actions it *does* understand, not blow up the drawer.\n */\nconst TOP_ACTION_BY_NAME: Record<string, TopActions> = {\n Category: TopActions.Category,\n Date: TopActions.Date,\n Memo: TopActions.Memo,\n Tags: TopActions.Tags,\n}\n\nconst BOTTOM_ACTION_BY_NAME: Record<string, BottomActions> = {\n MerchantBudget: BottomActions.MerchantBudget,\n SplitTransaction: BottomActions.SplitTransaction,\n HideTransaction: BottomActions.HideTransaction,\n MerchantLogo: BottomActions.MerchantLogo,\n}\n\nconst resolve = <T>(names: string[] | undefined, lookup: Record<string, T>, fallback: T[]): T[] => {\n // `undefined` means \"not configured\" -> defaults. An explicit empty array means \"no actions\",\n // which is a legitimate per-beat choice (a read-only details view) and must not fall back.\n if (!names) return fallback\n\n return names.map((name) => lookup[name]).filter((action): action is T => action !== undefined)\n}\n\nexport const resolveTopActions = (names?: string[]): TopActions[] =>\n resolve(names, TOP_ACTION_BY_NAME, DEFAULT_TOP_ACTIONS)\n\nexport const resolveBottomActions = (names?: string[]): BottomActions[] =>\n resolve(names, BOTTOM_ACTION_BY_NAME, DEFAULT_BOTTOM_ACTIONS)\n"],"mappings":";AAeA,IAAM,IAAiD;AAAA,EACrD,UAAU,EAAW;AAAA,EACrB,MAAM,EAAW;AAAA,EACjB,MAAM,EAAW;AAAA,EACjB,MAAM,EAAW;AACnB,GAEM,IAAuD;AAAA,EAC3D,gBAAgB,EAAc;AAAA,EAC9B,kBAAkB,EAAc;AAAA,EAChC,iBAAiB,EAAc;AAAA,EAC/B,cAAc,EAAc;AAC9B,GAEM,IAAA,CAAc,GAA6B,GAA2B,MAGrE,IAEE,EAAM,IAAA,CAAK,MAAS,EAAO,CAAA,CAAK,EAAE,OAAA,CAAQ,MAAwB,MAAW,MAAS,IAF1E,GAKR,IAAA,CAAqB,MAChC,EAAQ,GAAO,GAAoB,CAAmB,GAE3C,IAAA,CAAwB,MACnC,EAAQ,GAAO,GAAuB,CAAsB"}
@@ -0,0 +1,31 @@
1
+ import { Beat, Transaction } from '../../../core';
2
+ import { BottomActions, TopActions } from '../../../transactions/constants/Actions';
3
+ interface TransactionDetailsBlockState {
4
+ bottomActions: BottomActions[];
5
+ /** True while the store is still loading; the block shows a loader instead of an empty state. */
6
+ isLoading: boolean;
7
+ topActions: TopActions[];
8
+ /** `undefined` once loading finishes means the transaction could not be resolved. */
9
+ transaction?: Transaction;
10
+ }
11
+ /**
12
+ * Resolves which transaction this drawer is about, and which inline actions it offers.
13
+ *
14
+ * Resolution order is deliberate:
15
+ * 1. `instance_slot_data.transaction_guid` — the beat explicitly naming its subject.
16
+ * 2. `beat.primary_transaction_guid` — the standard beat-level pointer.
17
+ * 3. `beat.transaction_guids[0]` — last resort for beats that only carry the list.
18
+ *
19
+ * The guid is then looked up in `transactionStore.detailedTransactions` rather than used directly,
20
+ * because `TransactionDetails` needs the *detailed* shape (account + category + taggings joined in)
21
+ * and edits must write through to the same store object the rest of the app renders. `buildDetailed
22
+ * Transactions` inside the store is this repo's equivalent of pulse's `buildTransactionDetails`.
23
+ *
24
+ * `beat.primary_transaction` is the fallback of last resort: it is a payload snapshot, not a store
25
+ * object, so inline edits against it would not propagate. Preferring it over a store hit would make
26
+ * editing silently no-op, which is why it is only used when the store genuinely has nothing — and
27
+ * only when it is a snapshot of the *same* transaction the guid resolved to (or when no guid
28
+ * resolved at all). A snapshot of some other transaction is not an answer to this question.
29
+ */
30
+ export declare const useTransactionDetailsBlock: (beat: Beat) => TransactionDetailsBlockState;
31
+ export {};
@@ -0,0 +1,16 @@
1
+ import { useTransactionStore as e } from "../../../common/context/hooks.es.js";
2
+ import { resolveBottomActions as p, resolveTopActions as m } from "./transactionDetailsActions.es.js";
3
+ var g = (o) => {
4
+ const { detailedTransactions: a, isTransactionDataLoaded: i } = e(), s = o.insights_payload?.instance_slot_data ?? {}, t = s.transaction_guid ?? o.primary_transaction_guid ?? o.transaction_guids?.[0], r = t ? a.find((d) => d.guid === t) : void 0, n = o.primary_transaction, c = !t || n?.guid === t;
5
+ return {
6
+ bottomActions: p(s.bottom_actions),
7
+ isLoading: !i,
8
+ topActions: m(s.top_actions),
9
+ transaction: r ?? (c ? n : void 0)
10
+ };
11
+ };
12
+ export {
13
+ g as useTransactionDetailsBlock
14
+ };
15
+
16
+ //# sourceMappingURL=useTransactionDetailsBlock.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTransactionDetailsBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionDetailsBlock/useTransactionDetailsBlock.ts"],"sourcesContent":["import { useTransactionStore } from '../../../common'\nimport type { Beat, Transaction } from '../../../core'\nimport type { TransactionDetailsBlockData } from '../../../core/types/InsightsInstanceSlotData'\nimport type { BottomActions, TopActions } from '../../../transactions/constants/Actions'\n\nimport { resolveBottomActions, resolveTopActions } from './transactionDetailsActions'\n\ninterface TransactionDetailsBlockState {\n bottomActions: BottomActions[]\n /** True while the store is still loading; the block shows a loader instead of an empty state. */\n isLoading: boolean\n topActions: TopActions[]\n /** `undefined` once loading finishes means the transaction could not be resolved. */\n transaction?: Transaction\n}\n\n/**\n * Resolves which transaction this drawer is about, and which inline actions it offers.\n *\n * Resolution order is deliberate:\n * 1. `instance_slot_data.transaction_guid` — the beat explicitly naming its subject.\n * 2. `beat.primary_transaction_guid` — the standard beat-level pointer.\n * 3. `beat.transaction_guids[0]` — last resort for beats that only carry the list.\n *\n * The guid is then looked up in `transactionStore.detailedTransactions` rather than used directly,\n * because `TransactionDetails` needs the *detailed* shape (account + category + taggings joined in)\n * and edits must write through to the same store object the rest of the app renders. `buildDetailed\n * Transactions` inside the store is this repo's equivalent of pulse's `buildTransactionDetails`.\n *\n * `beat.primary_transaction` is the fallback of last resort: it is a payload snapshot, not a store\n * object, so inline edits against it would not propagate. Preferring it over a store hit would make\n * editing silently no-op, which is why it is only used when the store genuinely has nothing — and\n * only when it is a snapshot of the *same* transaction the guid resolved to (or when no guid\n * resolved at all). A snapshot of some other transaction is not an answer to this question.\n */\nexport const useTransactionDetailsBlock = (beat: Beat): TransactionDetailsBlockState => {\n const { detailedTransactions, isTransactionDataLoaded } = useTransactionStore()\n\n const slotData = (beat.insights_payload?.instance_slot_data ?? {}) as TransactionDetailsBlockData\n\n const guid =\n slotData.transaction_guid ?? beat.primary_transaction_guid ?? beat.transaction_guids?.[0]\n\n const fromStore = guid\n ? detailedTransactions.find((transaction) => transaction.guid === guid)\n : undefined\n\n // The snapshot is only a valid answer for the guid we actually resolved. When a beat names its\n // subject via instance_slot_data (or transaction_guids[0]) and that guid misses in the store,\n // falling back unconditionally would render a *different* transaction's details — and inline\n // edits would then be made against the wrong subject. Match on guid, or accept the snapshot only\n // when no guid resolved at all.\n const snapshot = beat.primary_transaction\n const snapshotMatches = !guid || snapshot?.guid === guid\n\n return {\n bottomActions: resolveBottomActions(slotData.bottom_actions),\n isLoading: !isTransactionDataLoaded,\n topActions: resolveTopActions(slotData.top_actions),\n transaction: fromStore ?? (snapshotMatches ? snapshot : undefined),\n }\n}\n"],"mappings":";;AAmCA,IAAa,IAAA,CAA8B,MAA6C;AACtF,QAAM,EAAE,sBAAA,GAAsB,yBAAA,EAAA,IAA4B,EAAoB,GAExE,IAAY,EAAK,kBAAkB,sBAAsB,CAAC,GAE1D,IACJ,EAAS,oBAAoB,EAAK,4BAA4B,EAAK,oBAAoB,CAAA,GAEnF,IAAY,IACd,EAAqB,KAAA,CAAM,MAAgB,EAAY,SAAS,CAAI,IACpE,QAOE,IAAW,EAAK,qBAChB,IAAkB,CAAC,KAAQ,GAAU,SAAS;AAEpD,SAAO;AAAA,IACL,eAAe,EAAqB,EAAS,cAAc;AAAA,IAC3D,WAAW,CAAC;AAAA,IACZ,YAAY,EAAkB,EAAS,WAAW;AAAA,IAClD,aAAa,MAAc,IAAkB,IAAW;AAAA,EAC1D;AACF"}
@@ -0,0 +1,15 @@
1
+ import { ActionBlockProps } from '../../mappings/blocks';
2
+ /**
3
+ * Open-time data guarantee for the transaction-details drawer. Wired as the block's `usePrepare`,
4
+ * so `ActionBlockLayer` runs it once each time a CTA opens the drawer (`ActionBlockRunner` mounts on
5
+ * open and unmounts on close).
6
+ *
7
+ * A beat can be rendered in a feed long before the transactions module has ever loaded — the feed
8
+ * does not depend on TransactionStore — so `detailedTransactions` is frequently empty at open time.
9
+ * `loadTransactionData` also pulls the accounts and categories that `buildDetailedTransactions`
10
+ * joins against, which is why it is preferred over the narrower `loadTransactions`.
11
+ *
12
+ * Guarded on `isTransactionDataLoaded` so re-opening the drawer does not refetch, and the store's
13
+ * own in-flight flags make a concurrent call a no-op.
14
+ */
15
+ export declare const useTransactionDetailsPrepare: (_props: ActionBlockProps) => void;
@@ -0,0 +1,13 @@
1
+ import { useTransactionStore as r } from "../../../common/context/hooks.es.js";
2
+ import t from "react";
3
+ var n = (e) => {
4
+ const { isTransactionDataLoaded: a, loadTransactionData: o } = r();
5
+ t.useEffect(() => {
6
+ a || o();
7
+ }, []);
8
+ };
9
+ export {
10
+ n as useTransactionDetailsPrepare
11
+ };
12
+
13
+ //# sourceMappingURL=useTransactionDetailsPrepare.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTransactionDetailsPrepare.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionDetailsBlock/useTransactionDetailsPrepare.ts"],"sourcesContent":["import React from 'react'\n\nimport { useTransactionStore } from '../../../common'\nimport type { ActionBlockProps } from '../../mappings/blocks'\n\n/**\n * Open-time data guarantee for the transaction-details drawer. Wired as the block's `usePrepare`,\n * so `ActionBlockLayer` runs it once each time a CTA opens the drawer (`ActionBlockRunner` mounts on\n * open and unmounts on close).\n *\n * A beat can be rendered in a feed long before the transactions module has ever loaded — the feed\n * does not depend on TransactionStore — so `detailedTransactions` is frequently empty at open time.\n * `loadTransactionData` also pulls the accounts and categories that `buildDetailedTransactions`\n * joins against, which is why it is preferred over the narrower `loadTransactions`.\n *\n * Guarded on `isTransactionDataLoaded` so re-opening the drawer does not refetch, and the store's\n * own in-flight flags make a concurrent call a no-op.\n */\nexport const useTransactionDetailsPrepare = (_props: ActionBlockProps) => {\n const { isTransactionDataLoaded, loadTransactionData } = useTransactionStore()\n\n React.useEffect(() => {\n if (!isTransactionDataLoaded) void loadTransactionData()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n}\n"],"mappings":";;AAkBA,IAAa,IAAA,CAAgC,MAA6B;AACxE,QAAM,EAAE,yBAAA,GAAyB,qBAAA,EAAA,IAAwB,EAAoB;AAE7E,EAAA,EAAM,UAAA,MAAgB;AACpB,IAAK,KAAyB,EAAyB;AAAA,EAEzD,GAAG,CAAC,CAAC;AACP"}
@@ -0,0 +1,22 @@
1
+ import { default as React } from 'react';
2
+ import { Transaction } from '../../../core';
3
+ interface TabItem {
4
+ label: string;
5
+ ariaLabel?: string;
6
+ }
7
+ interface TransactionDrillDownProps {
8
+ ariaLabel?: string;
9
+ heading?: string;
10
+ subHeadingText?: string;
11
+ icon?: React.ReactNode;
12
+ tabs?: TabItem[];
13
+ selectedIndex: number;
14
+ top: React.ReactNode;
15
+ onSelectionChange?: (selectedIndex: number) => void;
16
+ transactionFilter?: (selectedIndex: number) => (transaction: Transaction) => boolean;
17
+ baseTransactions?: Transaction[];
18
+ }
19
+ export declare const TransactionDrillDown: (({ ariaLabel, heading, subHeadingText, icon, tabs, selectedIndex, top, onSelectionChange, transactionFilter, baseTransactions, }: TransactionDrillDownProps) => import("react/jsx-runtime").JSX.Element) & {
20
+ displayName: string;
21
+ };
22
+ export default TransactionDrillDown;
@@ -0,0 +1,108 @@
1
+ import { useGlobalCopyStore as W, useGlobalUiStore as $, useTransactionStore as j } from "../../../common/context/hooks.es.js";
2
+ import { exportTransactionsToCSV as k } from "../../../core/utils/TransactionUtils.es.js";
3
+ import G from "../../../common/components/Loader.es.js";
4
+ import P from "../../../transactions/components/shared/TransactionList.es.js";
5
+ import "react";
6
+ import { observer as A } from "mobx-react-lite";
7
+ import c from "@mui/material/Stack";
8
+ import { H3 as E, Text as d } from "@mxenabled/mxui";
9
+ import N from "@mui/material/Button";
10
+ import { Icon as R } from "@mxenabled/mx-icons";
11
+ import { jsx as r, jsxs as o } from "react/jsx-runtime";
12
+ import p from "@mui/material/Box";
13
+ import U from "@mui/material/Tab";
14
+ import V from "@mui/material/Tabs";
15
+ var u = "transaction-drill-down-panel", v = (m) => `transaction-details-tab-${m}`, ar = A(function({ ariaLabel: T, heading: i, subHeadingText: n, icon: y, tabs: b, selectedIndex: e, top: C, onSelectionChange: g, transactionFilter: f, baseTransactions: _ }) {
16
+ const { transactions: l } = W(), { isCopyLoaded: w } = $(), { sortedTransactionsWithSplits: D, tags: S, detailedTransactions: L } = j();
17
+ if (!w) return /* @__PURE__ */ r(G, {});
18
+ const s = !!b?.length, B = T ?? l.transactions_title, I = (t, a) => {
19
+ g?.(a);
20
+ }, h = f ? (_ ?? L).filter(f(e)) : void 0, x = h ?? D;
21
+ return /* @__PURE__ */ o(p, {
22
+ sx: {
23
+ bgcolor: "background.paper",
24
+ width: "100%"
25
+ },
26
+ children: [s && /* @__PURE__ */ r(V, {
27
+ "aria-label": B,
28
+ onChange: I,
29
+ value: e,
30
+ variant: "fullWidth",
31
+ children: b.map((t, a) => /* @__PURE__ */ r(U, {
32
+ "aria-controls": u,
33
+ "aria-label": t.ariaLabel,
34
+ id: v(a),
35
+ label: t.label,
36
+ sx: {
37
+ minWidth: "auto",
38
+ px: 0
39
+ }
40
+ }, `${t.label}-${a}`))
41
+ }), /* @__PURE__ */ o(p, {
42
+ "aria-labelledby": s ? v(e) : void 0,
43
+ id: u,
44
+ role: s ? "tabpanel" : void 0,
45
+ children: [
46
+ (i || n) && /* @__PURE__ */ o(c, {
47
+ sx: {
48
+ gap: 4,
49
+ pt: 16,
50
+ px: 24
51
+ },
52
+ children: [i && /* @__PURE__ */ r(E, { children: i }), n && /* @__PURE__ */ o(c, {
53
+ sx: {
54
+ alignItems: "center",
55
+ flexDirection: "row",
56
+ gap: 8
57
+ },
58
+ children: [y, /* @__PURE__ */ r(d, {
59
+ textTransform: "uppercase",
60
+ variant: "body2",
61
+ children: n
62
+ })]
63
+ })]
64
+ }),
65
+ /* @__PURE__ */ r(p, {
66
+ sx: { pt: 16 },
67
+ children: C
68
+ }),
69
+ /* @__PURE__ */ o(c, {
70
+ sx: {
71
+ alignItems: "center",
72
+ borderBottomColor: "divider",
73
+ borderBottomStyle: "solid",
74
+ borderBottomWidth: 1,
75
+ flexDirection: "row",
76
+ justifyContent: "space-between",
77
+ pb: 4,
78
+ pt: 16,
79
+ px: 24
80
+ },
81
+ children: [/* @__PURE__ */ r(d, {
82
+ bold: !0,
83
+ variant: "body1",
84
+ children: l.transactions_title
85
+ }), x.length > 0 && /* @__PURE__ */ r(N, {
86
+ onClick: () => {
87
+ k(x, S);
88
+ },
89
+ startIcon: /* @__PURE__ */ r(R, { name: "ios_share" }),
90
+ sx: { height: 28 },
91
+ children: /* @__PURE__ */ r(d, {
92
+ bold: !0,
93
+ variant: "body2",
94
+ children: l.export_csv_btn
95
+ })
96
+ })]
97
+ }),
98
+ /* @__PURE__ */ r(P, { transaction: h })
99
+ ]
100
+ })]
101
+ });
102
+ });
103
+ export {
104
+ ar as TransactionDrillDown,
105
+ ar as default
106
+ };
107
+
108
+ //# sourceMappingURL=TransactionDrillDown.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransactionDrillDown.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionDrillDown.tsx"],"sourcesContent":["import React from 'react'\n\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Stack from '@mui/material/Stack'\nimport Tab from '@mui/material/Tab'\nimport Tabs from '@mui/material/Tabs'\nimport { observer } from 'mobx-react-lite'\n\nimport { Icon } from '@mxenabled/mx-icons'\nimport { H3, Text } from '@mxenabled/mxui'\n\nimport { Loader, useGlobalCopyStore, useGlobalUiStore, useTransactionStore } from '../../../common'\nimport type { Transaction } from '../../../core'\nimport { exportTransactionsToCSV } from '../../../core/utils/TransactionUtils'\nimport { TransactionList } from '../../../transactions'\n\ninterface TabItem {\n label: string\n ariaLabel?: string\n}\n\nconst PANEL_ID = 'transaction-drill-down-panel'\nconst tabId = (index: number) => `transaction-details-tab-${index}`\n\ninterface TransactionDrillDownProps {\n ariaLabel?: string\n heading?: string\n subHeadingText?: string\n icon?: React.ReactNode\n tabs?: TabItem[]\n selectedIndex: number\n top: React.ReactNode\n onSelectionChange?: (selectedIndex: number) => void\n transactionFilter?: (selectedIndex: number) => (transaction: Transaction) => boolean\n baseTransactions?: Transaction[]\n}\n\nexport const TransactionDrillDown = observer(function TransactionDrillDown({\n ariaLabel,\n heading,\n subHeadingText,\n icon,\n tabs,\n selectedIndex,\n top,\n onSelectionChange,\n transactionFilter,\n baseTransactions,\n}: TransactionDrillDownProps) {\n const { transactions: transactionsCopy } = useGlobalCopyStore()\n const { isCopyLoaded } = useGlobalUiStore()\n const { sortedTransactionsWithSplits, tags, detailedTransactions } = useTransactionStore()\n\n if (!isCopyLoaded) return <Loader />\n\n const hasTabs = !!tabs?.length\n const tablistLabel = ariaLabel ?? transactionsCopy.transactions_title\n\n const handlePrimaryChange = (_event: React.SyntheticEvent, newValue: number) => {\n onSelectionChange?.(newValue)\n }\n\n const filteredTransactions = transactionFilter\n ? (baseTransactions ?? detailedTransactions).filter(transactionFilter(selectedIndex))\n : undefined\n\n const exportTransactions = filteredTransactions ?? sortedTransactionsWithSplits\n\n return (\n <Box sx={{ bgcolor: 'background.paper', width: '100%' }}>\n {hasTabs && (\n <Tabs\n aria-label={tablistLabel}\n onChange={handlePrimaryChange}\n value={selectedIndex}\n variant=\"fullWidth\"\n >\n {tabs!.map((tab, index) => (\n <Tab\n aria-controls={PANEL_ID}\n aria-label={tab.ariaLabel}\n id={tabId(index)}\n key={`${tab.label}-${index}`}\n label={tab.label}\n sx={{ minWidth: 'auto', px: 0 }}\n />\n ))}\n </Tabs>\n )}\n\n <Box\n aria-labelledby={hasTabs ? tabId(selectedIndex) : undefined}\n id={PANEL_ID}\n role={hasTabs ? 'tabpanel' : undefined}\n >\n {(heading || subHeadingText) && (\n <Stack sx={{ gap: 4, pt: 16, px: 24 }}>\n {heading && <H3>{heading}</H3>}\n {subHeadingText && (\n <Stack sx={{ alignItems: 'center', flexDirection: 'row', gap: 8 }}>\n {icon}\n <Text textTransform=\"uppercase\" variant=\"body2\">\n {subHeadingText}\n </Text>\n </Stack>\n )}\n </Stack>\n )}\n\n <Box sx={{ pt: 16 }}>{top}</Box>\n\n <Stack\n sx={{\n alignItems: 'center',\n borderBottomColor: 'divider',\n borderBottomStyle: 'solid',\n borderBottomWidth: 1,\n flexDirection: 'row',\n justifyContent: 'space-between',\n pb: 4,\n pt: 16,\n px: 24,\n }}\n >\n <Text bold={true} variant=\"body1\">\n {transactionsCopy.transactions_title}\n </Text>\n {exportTransactions.length > 0 && (\n <Button\n onClick={() => {\n exportTransactionsToCSV(exportTransactions, tags)\n }}\n startIcon={<Icon name=\"ios_share\" />}\n sx={{ height: 28 }}\n >\n <Text bold={true} variant=\"body2\">\n {transactionsCopy.export_csv_btn}\n </Text>\n </Button>\n )}\n </Stack>\n\n <TransactionList transaction={filteredTransactions} />\n </Box>\n </Box>\n )\n})\n\nexport default TransactionDrillDown\n"],"mappings":";;;;;;;;;;;;;;AAsBA,IAAM,IAAW,gCACX,IAAA,CAAS,MAAkB,2BAA2B,CAAA,IAe/C,KAAuB,EAAS,SAA8B,EACzE,WAAA,GACA,SAAA,GACA,gBAAA,GACA,MAAA,GACA,MAAA,GACA,eAAA,GACA,KAAA,GACA,mBAAA,GACA,mBAAA,GACA,kBAAA,EAAA,GAC4B;AAC5B,QAAM,EAAE,cAAc,EAAA,IAAqB,EAAmB,GACxD,EAAE,cAAA,EAAA,IAAiB,EAAiB,GACpC,EAAE,8BAAA,GAA8B,MAAA,GAAM,sBAAA,EAAA,IAAyB,EAAoB;AAEzF,MAAI,CAAC,EAAc,QAAO,gBAAA,EAAC,GAAD,CAAS,CAAA;AAEnC,QAAM,IAAU,CAAC,CAAC,GAAM,QAClB,IAAe,KAAa,EAAiB,oBAE7C,IAAA,CAAuB,GAA8B,MAAqB;AAC9E,IAAA,IAAoB,CAAQ;AAAA,EAC9B,GAEM,IAAuB,KACxB,KAAoB,GAAsB,OAAO,EAAkB,CAAa,CAAC,IAClF,QAEE,IAAqB,KAAwB;AAEnD,SACE,gBAAA,EAAC,GAAD;AAAA,IAAK,IAAI;AAAA,MAAE,SAAS;AAAA,MAAoB,OAAO;AAAA,IAAO;AAAA,IAAtD,UAAA,CACG,KACC,gBAAA,EAAC,GAAD;AAAA,MACE,cAAY;AAAA,MACZ,UAAU;AAAA,MACV,OAAO;AAAA,MACP,SAAQ;AAAA,MAEP,UAAA,EAAM,IAAA,CAAK,GAAK,MACf,gBAAA,EAAC,GAAD;AAAA,QACE,iBAAe;AAAA,QACf,cAAY,EAAI;AAAA,QAChB,IAAI,EAAM,CAAK;AAAA,QAEf,OAAO,EAAI;AAAA,QACX,IAAI;AAAA,UAAE,UAAU;AAAA,UAAQ,IAAI;AAAA,QAAE;AAAA,MAC/B,GAHM,GAAG,EAAI,KAAA,IAAS,CAAA,EAGtB,CACF;AAAA,IACG,CAAA,GAGR,gBAAA,EAAC,GAAD;AAAA,MACE,mBAAiB,IAAU,EAAM,CAAa,IAAI;AAAA,MAClD,IAAI;AAAA,MACJ,MAAM,IAAU,aAAa;AAAA,MAH/B,UAAA;AAAA,SAKI,KAAW,MACX,gBAAA,EAAC,GAAD;AAAA,UAAO,IAAI;AAAA,YAAE,KAAK;AAAA,YAAG,IAAI;AAAA,YAAI,IAAI;AAAA,UAAG;AAAA,UAApC,UAAA,CACG,KAAW,gBAAA,EAAC,GAAD,EAAA,UAAK,EAAY,CAAA,GAC5B,KACC,gBAAA,EAAC,GAAD;AAAA,YAAO,IAAI;AAAA,cAAE,YAAY;AAAA,cAAU,eAAe;AAAA,cAAO,KAAK;AAAA,YAAE;AAAA,YAAhE,UAAA,CACG,GACD,gBAAA,EAAC,GAAD;AAAA,cAAM,eAAc;AAAA,cAAY,SAAQ;AAAA,cACrC,UAAA;AAAA,YACG,CAAA,CACD;AAAA,UAEJ,CAAA,CAAA;AAAA;QAGT,gBAAA,EAAC,GAAD;AAAA,UAAK,IAAI,EAAE,IAAI,GAAG;AAAA,UAAI,UAAA;AAAA,QAAS,CAAA;AAAA,QAE/B,gBAAA,EAAC,GAAD;AAAA,UACE,IAAI;AAAA,YACF,YAAY;AAAA,YACZ,mBAAmB;AAAA,YACnB,mBAAmB;AAAA,YACnB,mBAAmB;AAAA,YACnB,eAAe;AAAA,YACf,gBAAgB;AAAA,YAChB,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI;AAAA,UACN;AAAA,UAXF,UAAA,CAaE,gBAAA,EAAC,GAAD;AAAA,YAAM,MAAM;AAAA,YAAM,SAAQ;AAAA,YACvB,UAAA,EAAiB;AAAA,UACd,CAAA,GACL,EAAmB,SAAS,KAC3B,gBAAA,EAAC,GAAD;AAAA,YACE,SAAA,MAAe;AACb,cAAA,EAAwB,GAAoB,CAAI;AAAA,YAClD;AAAA,YACA,WAAW,gBAAA,EAAC,GAAD,EAAM,MAAK,YAAa,CAAA;AAAA,YACnC,IAAI,EAAE,QAAQ,GAAG;AAAA,YAEjB,UAAA,gBAAA,EAAC,GAAD;AAAA,cAAM,MAAM;AAAA,cAAM,SAAQ;AAAA,cACvB,UAAA,EAAiB;AAAA,YACd,CAAA;AAAA,UACA,CAAA,CAEL;AAAA;QAEP,gBAAA,EAAC,GAAD,EAAiB,aAAa,EAAuB,CAAA;AAAA,MAClD;AAAA,IACF,CAAA,CAAA;AAAA;AAET,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Beat } from '../../../core';
2
+ interface TransactionHistoryProp {
3
+ beat: Beat;
4
+ }
5
+ export declare const TransactionHistoryBlock: (({ beat }: TransactionHistoryProp) => import("react/jsx-runtime").JSX.Element) & {
6
+ displayName: string;
7
+ };
8
+ export {};
@@ -0,0 +1,89 @@
1
+ import { useInsightsV2Store as E } from "../../../common/context/hooks.es.js";
2
+ import { formatCurrency as M } from "../../../core/utils/NumberFormatting.es.js";
3
+ import { BarChartV2 as N } from "../../components/chartblocks/BarchartV2.es.js";
4
+ import { EmptyBarChart as P } from "../../components/chartblocks/EmptyBarChart.es.js";
5
+ import { TransactionDrillDown as R } from "./TransactionDrillDown.es.js";
6
+ import { useTransactionHistory as q } from "./useTransactionHistory.es.js";
7
+ import { getDefaultSelection as z, getDrillDownConfig as J, getFlattenedDataSeries as O, toStoreFilter as Q } from "./utils.es.js";
8
+ import { useLayoutEffect as U, useState as W } from "react";
9
+ import { observer as X } from "mobx-react-lite";
10
+ import { jsx as m } from "react/jsx-runtime";
11
+ import { useTheme as Y } from "@mui/material/styles";
12
+ var ct = X(({ beat: e }) => {
13
+ const g = Y(), { instanceSlotData: a, beatTransactions: n, primaryTransaction: T, spendCategoryGuids: u, allAccountGuids: p, categoryName: C, displayedDateRange: l, transactionStore: v } = q(e), { actionDrawer: h } = E(), { insights_payload: y } = e, D = h.trigger === "secondaryCta" ? y?.secondary_cta?.variant : y?.primary_cta?.variant, { top: c, filterMode: i, header: S, colors: x, filterStrategy: L } = J(D ?? ""), { dataSeries: s, chartData: o, comparisonBars: B } = O({
14
+ data: a?.data_series,
15
+ ariaLabel: a?.ariaLabel ?? "",
16
+ top: c,
17
+ beatTransactions: n,
18
+ payload: a
19
+ }), { chartBarIndex: I, subTabIndex: _ } = z({
20
+ chartTabCount: o.length,
21
+ comparisonTabCount: B.length,
22
+ actionDrawer: h,
23
+ beatGuid: e.guid
24
+ }), [r, b] = W(I), f = (t) => L({
25
+ beat: e,
26
+ beatTransactions: n,
27
+ dataSeries: s,
28
+ selectedIndex: t,
29
+ subIndex: _,
30
+ isLastTab: t === s.length - 1,
31
+ instanceSlotData: a,
32
+ spendCategoryGuids: u,
33
+ displayedDateRange: l
34
+ }), G = n.map((t) => t.guid).join(","), A = u.join(","), F = p.join(",");
35
+ U(() => {
36
+ i !== "store" || !s.length || v.setFilter(Q(f(r), {
37
+ allAccountGuids: p,
38
+ displayedDateRange: l
39
+ }));
40
+ }, [
41
+ e.guid,
42
+ r,
43
+ i,
44
+ s.length,
45
+ a,
46
+ l,
47
+ A,
48
+ F,
49
+ G
50
+ ]);
51
+ const d = S?.({
52
+ htmlTitle: e.html_title,
53
+ primaryTransaction: T,
54
+ categoryName: C,
55
+ selectedDate: o[r]?.date ?? void 0
56
+ }), j = x(g, a?.category_guid), [H, w] = (a?.legends ?? []).map((t) => t.label), K = i === "local" ? (t) => f(t).custom ?? (() => !0) : void 0, V = i === "local" ? n : void 0, k = c === "singleBar" || c === "upcomingBill" ? o.length ? /* @__PURE__ */ m(N, {
57
+ amounts: {
58
+ average: a?.average_amount,
59
+ formattedAverage: M(a?.average_amount ?? 0, "0,0.00")
60
+ },
61
+ color: j[0] ?? g.palette.primary.main,
62
+ data: o,
63
+ legendPosition: "top",
64
+ monthlyAmountLabel: H,
65
+ onBarClick: b,
66
+ selectedTabIndex: r,
67
+ trendAmountLabel: w
68
+ }) : /* @__PURE__ */ m(P, {}) : null;
69
+ return /* @__PURE__ */ m(R, {
70
+ ariaLabel: a?.ariaLabel,
71
+ baseTransactions: V,
72
+ heading: d?.heading,
73
+ icon: d?.icon,
74
+ onSelectionChange: b,
75
+ selectedIndex: r,
76
+ subHeadingText: d?.subHeadingText,
77
+ tabs: o.map((t) => ({
78
+ ariaLabel: t.ariaLabel,
79
+ label: t.label
80
+ })),
81
+ top: k,
82
+ transactionFilter: K
83
+ });
84
+ });
85
+ export {
86
+ ct as TransactionHistoryBlock
87
+ };
88
+
89
+ //# sourceMappingURL=TransactionHistoryBlock.es.js.map