@mx-cartographer/experiences 9.4.68 → 9.4.70

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 (40) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cashflow/context/hooks.d.ts +3 -0
  3. package/dist/common/utils/PostMessage.d.ts +5 -1
  4. package/dist/common/utils/PostMessage.es.js +26 -24
  5. package/dist/common/utils/PostMessage.es.js.map +1 -1
  6. package/dist/core/stores/CashflowStore.d.ts +3 -0
  7. package/dist/core/stores/CashflowStore.es.js +9 -0
  8. package/dist/core/stores/CashflowStore.es.js.map +1 -1
  9. package/dist/core/types/Insights.d.ts +17 -0
  10. package/dist/core/types/index.d.ts +1 -1
  11. package/dist/core/types/localization/InsightsFeedCopy.d.ts +15 -0
  12. package/dist/core/types/localization/index.d.ts +1 -1
  13. package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/index.d.ts +1 -1
  14. package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashBlock.d.ts +11 -0
  15. package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashBlock.es.js +89 -0
  16. package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashBlock.es.js.map +1 -0
  17. package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashContent.d.ts +32 -0
  18. package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashContent.es.js +169 -0
  19. package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashContent.es.js.map +1 -0
  20. package/dist/insights-v2/actionBlocks/RemainingCashBlock/index.d.ts +1 -0
  21. package/dist/insights-v2/actionBlocks/RemainingCashBlock/layout.d.ts +27 -0
  22. package/dist/insights-v2/actionBlocks/RemainingCashBlock/layout.es.js +26 -0
  23. package/dist/insights-v2/actionBlocks/RemainingCashBlock/layout.es.js.map +1 -0
  24. package/dist/insights-v2/components/BaseLayout.es.js.map +1 -1
  25. package/dist/insights-v2/ctaActions/ctaConfig.d.ts +3 -2
  26. package/dist/insights-v2/ctaActions/ctaConfig.es.js.map +1 -1
  27. package/dist/insights-v2/ctaActions/ctaPostMessages.d.ts +16 -0
  28. package/dist/insights-v2/ctaActions/ctaPostMessages.es.js +51 -0
  29. package/dist/insights-v2/ctaActions/ctaPostMessages.es.js.map +1 -0
  30. package/dist/insights-v2/ctaActions/index.d.ts +1 -0
  31. package/dist/insights-v2/ctaActions/runCtaAction.d.ts +5 -1
  32. package/dist/insights-v2/ctaActions/runCtaAction.es.js +54 -51
  33. package/dist/insights-v2/ctaActions/runCtaAction.es.js.map +1 -1
  34. package/dist/insights-v2/ctaActions/types.d.ts +1 -1
  35. package/dist/insights-v2/mappings/actionBlocksKey.d.ts +1 -0
  36. package/dist/insights-v2/mappings/actionBlocksKey.es.js +2 -1
  37. package/dist/insights-v2/mappings/actionBlocksKey.es.js.map +1 -1
  38. package/dist/insights-v2/mappings/blocks.es.js +48 -42
  39. package/dist/insights-v2/mappings/blocks.es.js.map +1 -1
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [9.4.70] - 10-09-2026
2
+
3
+ - **ADDED** - `PrimaryCTA` / `SecondaryCTA` accept a backend-described `metadata` object (`event`, `action`, `payload`); on press the CTA runner posts that message to the host after the step's handler and effects succeed, so per-insight post messages no longer need frontend code (WEC-1852)
4
+ - **CHANGED** - A CTA press posts a message only when one resolves (`cta.metadata`, else `ctaPostMessages`); the previous fallback that posted a base `beat/cta` `op_1` for every block-less CTA is removed (WEC-1852)
5
+ - **CHANGED** - `sendBeatCta` accepts an `event` override and `extras` merged into the message metadata (WEC-1852)
6
+ - **ADDED** - `ctaPostMessages`: interim per-template CTA post messages (SaveAnExtra100Dollars, LowAccountBalance, MonthlyObligationsStatus, DesignateEmergencySavingsAccount, ReplenishSavings, SavingsOpportunityV2) carried over from pulse, used only when the backend sends no `cta.metadata` (WEC-1852)
7
+
8
+ ## [9.4.69] - 10-09-2026
9
+
10
+ - **ADDED** - `remaining_cash_block` (Remaining Cash) action block for the Payday Cash Left Over Status beat: summary card (remaining until payday, days, cash on hand, upcoming expenses) plus cash accounts and recurring expenses before payday, each capped at two rows with an "Other (#)" row. Store-driven from `CashflowStore`; loads through `useRecurringTransactionPrepare` with an `ErrorCard` retry on failure (WEC-1880)
11
+ - **ADDED** - `CashflowStore.uiStore` computeds `upcomingExpensesTotal`, `remainingUntilPayday`, `hasUpcomingPayday`; `insights_feed.remaining_cash` copy (WEC-1880)
12
+ - **ADDED** - Payday Cash Left Over Status beat fixtures, `InsightsV2/ActionBlock/RemainingCashBlock` stories, and `docs/action-blocks/remaining-cash-block.md` (WEC-1880)
13
+
1
14
  ## [9.4.68] - 10-09-2026
2
15
 
3
16
  - **ADDED** - `transaction_list_block` (Transaction List) action block: opens a date-grouped drawer of the transactions a beat points at, with a nested Transaction Details drawer stacked on row click. Resolves rows via `instance_slot_data.transaction_guids` (id-list path), guarded by `Array.isArray` at the untrusted-payload boundary. `usePrepare` shares `useTransactionDetailsPrepare` so `detailedTransactions` is loaded on open — the drawer never opens on a permanent spinner. The registry entry titles the drawer from `insights_payload.title` (matching the WEC-1804 move off `beat.html_title`, which carries HTML entities); when the resolver returns an empty string, `ActionBlockLayer` falls back to the CTA label. Nested close clears the shared `transaction_guid` from action context, matching the sibling `TransactionHistoryBlock` convention. Only the `Standard` variant ships in this MR; the other three variants (`w/Summary`, `w/Status`, `w/Help`) are deferred to a follow-up MR after Figma review surfaced structural mismatches. Unblocks WEC-1461 and WEC-1462. (WEC-1425)
@@ -13,4 +13,7 @@ export declare const useCashflowUiStore: () => {
13
13
  get selectedCashBalance(): number;
14
14
  get daysUntilNextIncome(): number;
15
15
  get expensesTilNextIncome(): import('../../core').Recurrence[];
16
+ get upcomingExpensesTotal(): number;
17
+ get remainingUntilPayday(): number;
18
+ get hasUpcomingPayday(): boolean;
16
19
  };
@@ -52,10 +52,14 @@ export interface SendBeatCtaOptions {
52
52
  userGuid?: string;
53
53
  /** Operation string sent with the CTA (defaults to op_1). */
54
54
  op?: string;
55
+ /** Post message event name (defaults to `beat/cta`). */
56
+ event?: string;
57
+ /** Beat-specific fields merged into the metadata after the base payload and `user_guid`. */
58
+ extras?: Record<string, unknown>;
55
59
  }
56
60
  /**
57
61
  * Send a beat CTA to the host: builds the payload, adds `user_guid`, then either
58
62
  * posts a message to the host or navigates the webview. Mirrors the behaviour of
59
63
  * Pulse's `postMessages` epic (minus the RxJS buffering).
60
64
  */
61
- export declare function sendBeatCta(beat: BeatCtaSource, { isMobileWebview, webviewScheme, userGuid, op }?: SendBeatCtaOptions): void;
65
+ export declare function sendBeatCta(beat: BeatCtaSource, { isMobileWebview, webviewScheme, userGuid, op, event, extras }?: SendBeatCtaOptions): void;
@@ -1,62 +1,64 @@
1
- function c() {
1
+ function f() {
2
2
  return window.opener && window.opener.location ? window.opener.location.toString() : document.referrer;
3
3
  }
4
4
  function a(e) {
5
- const n = c().replace(/([^:]+:\/\/[^\/]+).*/, "$1"), t = window.parent || window.opener || !1;
5
+ const n = f().replace(/([^:]+:\/\/[^\/]+).*/, "$1"), t = window.parent || window.opener || !1;
6
6
  return t && t.postMessage && n ? (t.postMessage(e, n), !0) : !1;
7
7
  }
8
- function w(e) {
8
+ function T(e) {
9
9
  return a({
10
10
  metadata: { beat_guid: e },
11
11
  mx: !0,
12
12
  type: "mx/moneyDashboard/insightOnTransaction"
13
13
  });
14
14
  }
15
- function m(e) {
15
+ function y(e) {
16
16
  return a({
17
17
  metadata: { template: e },
18
18
  mx: !0,
19
19
  type: "mx/pulse/client_configured_cta"
20
20
  });
21
21
  }
22
- var d = "beat/cta", p = "op_1";
23
- function f(e, n = p) {
22
+ var l = "beat/cta", g = "op_1", x = "op_2";
23
+ function w(e, n = g) {
24
24
  const t = {
25
25
  action: n,
26
26
  beat_guid: e.guid,
27
27
  beat_template: e.template
28
28
  };
29
- return e?.payload?.action_string && (t.action_string = e.payload.action_string), [d, t];
29
+ return e?.payload?.action_string && (t.action_string = e.payload.action_string), [l, t];
30
30
  }
31
- function l(e, n, t = "mx/pulse") {
31
+ function m(e, n, t = "mx/pulse") {
32
32
  return window.self === window.top ? !1 : a({
33
33
  metadata: n,
34
34
  mx: !0,
35
35
  type: `${t}/${e}`
36
36
  });
37
37
  }
38
- function g(e, n, t = "mx") {
38
+ function _(e, n, t = "mx") {
39
39
  let o = `${t}://pulse/${e}`;
40
40
  n && (o += "?metadata=" + encodeURIComponent(JSON.stringify(n))), window.location.href = o;
41
41
  }
42
- function _(e, { isMobileWebview: n, webviewScheme: t, userGuid: o, op: s } = {}) {
43
- const [r, u] = f(e, s), i = {
44
- ...u,
45
- ...o ? { user_guid: o } : {}
46
- };
47
- n ? g(r, i, t) : l(r, i);
42
+ function E(e, { isMobileWebview: n, webviewScheme: t, userGuid: o, op: s, event: u, extras: c } = {}) {
43
+ const [d, p] = w(e, s), r = {
44
+ ...p,
45
+ ...o ? { user_guid: o } : {},
46
+ ...c
47
+ }, i = u || d;
48
+ n ? _(i, r, t) : m(i, r);
48
49
  }
49
50
  export {
50
- d as BEAT_CTA_EVENT,
51
- p as BEAT_OPERATION_1,
52
- f as getBaseCtaPostMessage,
53
- c as getReferrer,
54
- m as postClientConfiguredCta,
55
- w as postInsightOnTransaction,
51
+ l as BEAT_CTA_EVENT,
52
+ g as BEAT_OPERATION_1,
53
+ x as BEAT_OPERATION_2,
54
+ w as getBaseCtaPostMessage,
55
+ f as getReferrer,
56
+ y as postClientConfiguredCta,
57
+ T as postInsightOnTransaction,
56
58
  a as postMessageToParent,
57
- _ as sendBeatCta,
58
- l as sendBeatPostMessage,
59
- g as setWebviewURL
59
+ E as sendBeatCta,
60
+ m as sendBeatPostMessage,
61
+ _ as setWebviewURL
60
62
  };
61
63
 
62
64
  //# sourceMappingURL=PostMessage.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PostMessage.es.js","names":[],"sources":["../../../src/common/utils/PostMessage.ts"],"sourcesContent":["export function getReferrer(): string {\n if (window.opener && window.opener.location) {\n return window.opener.location.toString()\n } else {\n return document.referrer\n }\n}\n\nexport interface PostMessageData {\n metadata?: Record<string, any>\n mx: boolean\n type: string\n [key: string]: any\n}\n\nexport function postMessageToParent(message: PostMessageData): boolean {\n const postUrl = getReferrer().replace(/([^:]+:\\/\\/[^\\/]+).*/, '$1') // eslint-disable-line\n const otherWindow = window.parent || window.opener || false\n\n if (otherWindow && otherWindow.postMessage && postUrl) {\n otherWindow.postMessage(message, postUrl)\n return true\n }\n\n return false\n}\n\nexport function postInsightOnTransaction(beatGuid: string): boolean {\n const message: PostMessageData = {\n metadata: { beat_guid: beatGuid },\n mx: true,\n type: 'mx/moneyDashboard/insightOnTransaction',\n }\n\n return postMessageToParent(message)\n}\n\nexport function postClientConfiguredCta(template: string): boolean {\n const message: PostMessageData = {\n metadata: { template: template },\n mx: true,\n type: 'mx/pulse/client_configured_cta',\n }\n\n return postMessageToParent(message)\n}\n\n// ============================================================================\n// Beat CTA post messages (ported from Pulse: src/utils/PostMessage.js +\n// src/constants/PostMessage.js + src/epics/PostMessage.js).\n//\n// Scope: currently only the Save an extra $100 beat uses this. When a beat CTA\n// has no drilldown drawer, clicking it notifies the host app via a beat/cta\n// post message instead of opening a drawer. Native (mobile webview) hosts can't\n// receive post messages, so we navigate the webview to a custom-scheme URL.\n// ============================================================================\n\nexport const BEAT_CTA_EVENT = 'beat/cta'\nexport const BEAT_OPERATION_1 = 'op_1'\nexport const BEAT_OPERATION_2 = 'op_2'\nexport const BEAT_OPERATION_3 = 'op_3'\n\n/** Minimal beat shape needed to build a CTA post message. */\nexport interface BeatCtaSource {\n guid: string\n template: string\n payload?: { action_string?: string } | null\n}\n\nexport interface BeatCtaPayload {\n action: string\n beat_guid: string\n beat_template: string\n action_string?: string\n [key: string]: unknown\n}\n\n/**\n * Build the `[event, payload]` tuple for a beat CTA post message.\n * Mirrors Pulse's `getBaseCtaPostMessage`.\n */\nexport function getBaseCtaPostMessage(\n beat: BeatCtaSource,\n op: string = BEAT_OPERATION_1,\n): [string, BeatCtaPayload] {\n const payload: BeatCtaPayload = {\n action: op,\n beat_guid: beat.guid,\n beat_template: beat.template,\n }\n\n if (beat?.payload?.action_string) {\n payload.action_string = beat.payload.action_string\n }\n\n return [BEAT_CTA_EVENT, payload]\n}\n\n/**\n * Send a beat post message to the parent window. Only posts when embedded in an\n * iframe (`window.self !== window.top`), mirroring Pulse's `sendPostMessage`\n * (default scheme `mx/pulse`). Returns false when not embedded or no target.\n */\nexport function sendBeatPostMessage(\n event: string,\n data: Record<string, unknown>,\n scheme = 'mx/pulse',\n): boolean {\n if (window.self === window.top) {\n return false\n }\n\n const message: PostMessageData = {\n metadata: data,\n mx: true,\n type: `${scheme}/${event}`,\n }\n\n return postMessageToParent(message)\n}\n\n/**\n * Navigate a mobile webview to a custom-scheme URL instead of posting a message.\n * Mirrors Pulse's `setWebviewURL` (default scheme `mx`).\n */\nexport function setWebviewURL(event: string, data?: Record<string, unknown>, scheme = 'mx'): void {\n let webviewUrl = `${scheme}://pulse/${event}`\n\n if (data) {\n webviewUrl += '?metadata=' + encodeURIComponent(JSON.stringify(data))\n }\n\n window.location.href = webviewUrl\n}\n\nexport interface SendBeatCtaOptions {\n /** True when running inside a native mobile webview. */\n isMobileWebview?: boolean\n /** Custom URL scheme for webview navigation (from client config). */\n webviewScheme?: string\n /** Current user guid, added to the metadata like the Pulse epic does. */\n userGuid?: string\n /** Operation string sent with the CTA (defaults to op_1). */\n op?: string\n}\n\n/**\n * Send a beat CTA to the host: builds the payload, adds `user_guid`, then either\n * posts a message to the host or navigates the webview. Mirrors the behaviour of\n * Pulse's `postMessages` epic (minus the RxJS buffering).\n */\nexport function sendBeatCta(\n beat: BeatCtaSource,\n { isMobileWebview, webviewScheme, userGuid, op }: SendBeatCtaOptions = {},\n): void {\n const [event, basePayload] = getBaseCtaPostMessage(beat, op)\n const metadata: BeatCtaPayload = {\n ...basePayload,\n ...(userGuid ? { user_guid: userGuid } : {}),\n }\n\n if (isMobileWebview) {\n setWebviewURL(event, metadata, webviewScheme)\n } else {\n sendBeatPostMessage(event, metadata)\n }\n}\n"],"mappings":"AAAA,SAAgB,IAAsB;AACpC,SAAI,OAAO,UAAU,OAAO,OAAO,WAC1B,OAAO,OAAO,SAAS,SAAS,IAEhC,SAAS;AAEpB;AASA,SAAgB,EAAoB,GAAmC;AACrE,QAAM,IAAU,EAAY,EAAE,QAAQ,wBAAwB,IAAI,GAC5D,IAAc,OAAO,UAAU,OAAO,UAAU;AAEtD,SAAI,KAAe,EAAY,eAAe,KAC5C,EAAY,YAAY,GAAS,CAAO,GACjC,MAGF;AACT;AAEA,SAAgB,EAAyB,GAA2B;AAOlE,SAAO,EAAoB;AAAA,IALzB,UAAU,EAAE,WAAW,EAAS;AAAA,IAChC,IAAI;AAAA,IACJ,MAAM;AAAA,EAGmB,CAAO;AACpC;AAEA,SAAgB,EAAwB,GAA2B;AAOjE,SAAO,EAAoB;AAAA,IALzB,UAAU,EAAY,UAAA,EAAS;AAAA,IAC/B,IAAI;AAAA,IACJ,MAAM;AAAA,EAGmB,CAAO;AACpC;AAYA,IAAa,IAAiB,YACjB,IAAmB;AAuBhC,SAAgB,EACd,GACA,IAAa,GACa;AAC1B,QAAM,IAA0B;AAAA,IAC9B,QAAQ;AAAA,IACR,WAAW,EAAK;AAAA,IAChB,eAAe,EAAK;AAAA,EACtB;AAEA,SAAI,GAAM,SAAS,kBACjB,EAAQ,gBAAgB,EAAK,QAAQ,gBAGhC,CAAC,GAAgB,CAAO;AACjC;AAOA,SAAgB,EACd,GACA,GACA,IAAS,YACA;AACT,SAAI,OAAO,SAAS,OAAO,MAClB,KASF,EAAoB;AAAA,IALzB,UAAU;AAAA,IACV,IAAI;AAAA,IACJ,MAAM,GAAG,CAAA,IAAU,CAAA;AAAA,EAGM,CAAO;AACpC;AAMA,SAAgB,EAAc,GAAe,GAAgC,IAAS,MAAY;AAChG,MAAI,IAAa,GAAG,CAAA,YAAkB,CAAA;AAEtC,EAAI,MACF,KAAc,eAAe,mBAAmB,KAAK,UAAU,CAAI,CAAC,IAGtE,OAAO,SAAS,OAAO;AACzB;AAkBA,SAAgB,EACd,GACA,EAAE,iBAAA,GAAiB,eAAA,GAAe,UAAA,GAAU,IAAA,EAAA,IAA2B,CAAC,GAClE;AACN,QAAM,CAAC,GAAO,CAAA,IAAe,EAAsB,GAAM,CAAE,GACrD,IAA2B;AAAA,IAC/B,GAAG;AAAA,IACH,GAAI,IAAW,EAAE,WAAW,EAAS,IAAI,CAAC;AAAA,EAC5C;AAEA,EAAI,IACF,EAAc,GAAO,GAAU,CAAa,IAE5C,EAAoB,GAAO,CAAQ;AAEvC"}
1
+ {"version":3,"file":"PostMessage.es.js","names":[],"sources":["../../../src/common/utils/PostMessage.ts"],"sourcesContent":["export function getReferrer(): string {\n if (window.opener && window.opener.location) {\n return window.opener.location.toString()\n } else {\n return document.referrer\n }\n}\n\nexport interface PostMessageData {\n metadata?: Record<string, any>\n mx: boolean\n type: string\n [key: string]: any\n}\n\nexport function postMessageToParent(message: PostMessageData): boolean {\n const postUrl = getReferrer().replace(/([^:]+:\\/\\/[^\\/]+).*/, '$1') // eslint-disable-line\n const otherWindow = window.parent || window.opener || false\n\n if (otherWindow && otherWindow.postMessage && postUrl) {\n otherWindow.postMessage(message, postUrl)\n return true\n }\n\n return false\n}\n\nexport function postInsightOnTransaction(beatGuid: string): boolean {\n const message: PostMessageData = {\n metadata: { beat_guid: beatGuid },\n mx: true,\n type: 'mx/moneyDashboard/insightOnTransaction',\n }\n\n return postMessageToParent(message)\n}\n\nexport function postClientConfiguredCta(template: string): boolean {\n const message: PostMessageData = {\n metadata: { template: template },\n mx: true,\n type: 'mx/pulse/client_configured_cta',\n }\n\n return postMessageToParent(message)\n}\n\n// ============================================================================\n// Beat CTA post messages (ported from Pulse: src/utils/PostMessage.js +\n// src/constants/PostMessage.js + src/epics/PostMessage.js).\n//\n// Scope: currently only the Save an extra $100 beat uses this. When a beat CTA\n// has no drilldown drawer, clicking it notifies the host app via a beat/cta\n// post message instead of opening a drawer. Native (mobile webview) hosts can't\n// receive post messages, so we navigate the webview to a custom-scheme URL.\n// ============================================================================\n\nexport const BEAT_CTA_EVENT = 'beat/cta'\nexport const BEAT_OPERATION_1 = 'op_1'\nexport const BEAT_OPERATION_2 = 'op_2'\nexport const BEAT_OPERATION_3 = 'op_3'\n\n/** Minimal beat shape needed to build a CTA post message. */\nexport interface BeatCtaSource {\n guid: string\n template: string\n payload?: { action_string?: string } | null\n}\n\nexport interface BeatCtaPayload {\n action: string\n beat_guid: string\n beat_template: string\n action_string?: string\n [key: string]: unknown\n}\n\n/**\n * Build the `[event, payload]` tuple for a beat CTA post message.\n * Mirrors Pulse's `getBaseCtaPostMessage`.\n */\nexport function getBaseCtaPostMessage(\n beat: BeatCtaSource,\n op: string = BEAT_OPERATION_1,\n): [string, BeatCtaPayload] {\n const payload: BeatCtaPayload = {\n action: op,\n beat_guid: beat.guid,\n beat_template: beat.template,\n }\n\n if (beat?.payload?.action_string) {\n payload.action_string = beat.payload.action_string\n }\n\n return [BEAT_CTA_EVENT, payload]\n}\n\n/**\n * Send a beat post message to the parent window. Only posts when embedded in an\n * iframe (`window.self !== window.top`), mirroring Pulse's `sendPostMessage`\n * (default scheme `mx/pulse`). Returns false when not embedded or no target.\n */\nexport function sendBeatPostMessage(\n event: string,\n data: Record<string, unknown>,\n scheme = 'mx/pulse',\n): boolean {\n if (window.self === window.top) {\n return false\n }\n\n const message: PostMessageData = {\n metadata: data,\n mx: true,\n type: `${scheme}/${event}`,\n }\n\n return postMessageToParent(message)\n}\n\n/**\n * Navigate a mobile webview to a custom-scheme URL instead of posting a message.\n * Mirrors Pulse's `setWebviewURL` (default scheme `mx`).\n */\nexport function setWebviewURL(event: string, data?: Record<string, unknown>, scheme = 'mx'): void {\n let webviewUrl = `${scheme}://pulse/${event}`\n\n if (data) {\n webviewUrl += '?metadata=' + encodeURIComponent(JSON.stringify(data))\n }\n\n window.location.href = webviewUrl\n}\n\nexport interface SendBeatCtaOptions {\n /** True when running inside a native mobile webview. */\n isMobileWebview?: boolean\n /** Custom URL scheme for webview navigation (from client config). */\n webviewScheme?: string\n /** Current user guid, added to the metadata like the Pulse epic does. */\n userGuid?: string\n /** Operation string sent with the CTA (defaults to op_1). */\n op?: string\n /** Post message event name (defaults to `beat/cta`). */\n event?: string\n /** Beat-specific fields merged into the metadata after the base payload and `user_guid`. */\n extras?: Record<string, unknown>\n}\n\n/**\n * Send a beat CTA to the host: builds the payload, adds `user_guid`, then either\n * posts a message to the host or navigates the webview. Mirrors the behaviour of\n * Pulse's `postMessages` epic (minus the RxJS buffering).\n */\nexport function sendBeatCta(\n beat: BeatCtaSource,\n { isMobileWebview, webviewScheme, userGuid, op, event, extras }: SendBeatCtaOptions = {},\n): void {\n const [defaultEvent, basePayload] = getBaseCtaPostMessage(beat, op)\n const metadata: BeatCtaPayload = {\n ...basePayload,\n ...(userGuid ? { user_guid: userGuid } : {}),\n ...extras,\n }\n const eventName = event || defaultEvent\n\n if (isMobileWebview) {\n setWebviewURL(eventName, metadata, webviewScheme)\n } else {\n sendBeatPostMessage(eventName, metadata)\n }\n}\n"],"mappings":"AAAA,SAAgB,IAAsB;AACpC,SAAI,OAAO,UAAU,OAAO,OAAO,WAC1B,OAAO,OAAO,SAAS,SAAS,IAEhC,SAAS;AAEpB;AASA,SAAgB,EAAoB,GAAmC;AACrE,QAAM,IAAU,EAAY,EAAE,QAAQ,wBAAwB,IAAI,GAC5D,IAAc,OAAO,UAAU,OAAO,UAAU;AAEtD,SAAI,KAAe,EAAY,eAAe,KAC5C,EAAY,YAAY,GAAS,CAAO,GACjC,MAGF;AACT;AAEA,SAAgB,EAAyB,GAA2B;AAOlE,SAAO,EAAoB;AAAA,IALzB,UAAU,EAAE,WAAW,EAAS;AAAA,IAChC,IAAI;AAAA,IACJ,MAAM;AAAA,EAGmB,CAAO;AACpC;AAEA,SAAgB,EAAwB,GAA2B;AAOjE,SAAO,EAAoB;AAAA,IALzB,UAAU,EAAY,UAAA,EAAS;AAAA,IAC/B,IAAI;AAAA,IACJ,MAAM;AAAA,EAGmB,CAAO;AACpC;AAYA,IAAa,IAAiB,YACjB,IAAmB,QACnB,IAAmB;AAsBhC,SAAgB,EACd,GACA,IAAa,GACa;AAC1B,QAAM,IAA0B;AAAA,IAC9B,QAAQ;AAAA,IACR,WAAW,EAAK;AAAA,IAChB,eAAe,EAAK;AAAA,EACtB;AAEA,SAAI,GAAM,SAAS,kBACjB,EAAQ,gBAAgB,EAAK,QAAQ,gBAGhC,CAAC,GAAgB,CAAO;AACjC;AAOA,SAAgB,EACd,GACA,GACA,IAAS,YACA;AACT,SAAI,OAAO,SAAS,OAAO,MAClB,KASF,EAAoB;AAAA,IALzB,UAAU;AAAA,IACV,IAAI;AAAA,IACJ,MAAM,GAAG,CAAA,IAAU,CAAA;AAAA,EAGM,CAAO;AACpC;AAMA,SAAgB,EAAc,GAAe,GAAgC,IAAS,MAAY;AAChG,MAAI,IAAa,GAAG,CAAA,YAAkB,CAAA;AAEtC,EAAI,MACF,KAAc,eAAe,mBAAmB,KAAK,UAAU,CAAI,CAAC,IAGtE,OAAO,SAAS,OAAO;AACzB;AAsBA,SAAgB,EACd,GACA,EAAE,iBAAA,GAAiB,eAAA,GAAe,UAAA,GAAU,IAAA,GAAI,OAAA,GAAO,QAAA,EAAA,IAA+B,CAAC,GACjF;AACN,QAAM,CAAC,GAAc,CAAA,IAAe,EAAsB,GAAM,CAAE,GAC5D,IAA2B;AAAA,IAC/B,GAAG;AAAA,IACH,GAAI,IAAW,EAAE,WAAW,EAAS,IAAI,CAAC;AAAA,IAC1C,GAAG;AAAA,EACL,GACM,IAAY,KAAS;AAE3B,EAAI,IACF,EAAc,GAAW,GAAU,CAAa,IAEhD,EAAoB,GAAW,CAAQ;AAE3C"}
@@ -22,5 +22,8 @@ declare class UiStore {
22
22
  get selectedCashBalance(): number;
23
23
  get daysUntilNextIncome(): number;
24
24
  get expensesTilNextIncome(): Recurrence[];
25
+ get upcomingExpensesTotal(): number;
26
+ get remainingUntilPayday(): number;
27
+ get hasUpcomingPayday(): boolean;
25
28
  }
26
29
  export {};
@@ -54,6 +54,15 @@ var f = class {
54
54
  get expensesTilNextIncome() {
55
55
  return this.upcomingExpenses.filter((e) => c(e.expectedDate, this.nextIncome?.expectedDate || i(/* @__PURE__ */ new Date())));
56
56
  }
57
+ get upcomingExpensesTotal() {
58
+ return this.expensesTilNextIncome.reduce((e, t) => e + t.amount, 0);
59
+ }
60
+ get remainingUntilPayday() {
61
+ return this.selectedCashBalance - this.upcomingExpensesTotal;
62
+ }
63
+ get hasUpcomingPayday() {
64
+ return this.nextIncome !== void 0;
65
+ }
57
66
  };
58
67
  export {
59
68
  f as CashflowStore
@@ -1 +1 @@
1
- {"version":3,"file":"CashflowStore.es.js","names":[],"sources":["../../../src/core/stores/CashflowStore.ts"],"sourcesContent":["import { makeAutoObservable } from 'mobx'\nimport { addDays } from 'date-fns/addDays'\nimport { differenceInDays } from 'date-fns/differenceInDays'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { isBefore } from 'date-fns/isBefore'\nimport { startOfToday } from 'date-fns/startOfToday'\n\nimport type { Account, Recurrence, UserProfile } from '../types'\nimport { getAccountStartingBalance } from '../utils/AccountUtils'\n\nimport { GlobalStore } from './GlobalStore'\n\nexport class CashflowStore {\n globalStore: GlobalStore\n uiStore: UiStore\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n this.uiStore = new UiStore(globalStore)\n\n makeAutoObservable(this)\n }\n\n completeOnboarding = async () => {\n const updatedUserProfile = {\n ...this.globalStore.userStore.userProfile,\n has_completed_cash_flow_onboarding: true,\n } as UserProfile\n\n await this.globalStore.userStore.updateUserProfile(updatedUserProfile)\n }\n}\n\nclass UiStore {\n globalStore: GlobalStore\n\n // Cashflow is hard-coded to display 30 days starting from today. This is the\n // single source of truth for Cash Flow's window; it intentionally does NOT\n // mutate RecurringTransactionsStore.dateRange, so Cash Flow stays decoupled\n // from other widgets (e.g. Recurring Transactions) that drive that shared range.\n dateRange = { start: startOfToday(), end: addDays(startOfToday(), 30) }\n isDirty = false\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n\n makeAutoObservable(this)\n }\n\n // Recurrence data scoped to Cash Flow's own dateRange, sourced from the\n // range-parameterized helpers on RecurringTransactionsStore. Computeds (not\n // component state) per the project's MobX conventions.\n get income(): Recurrence[] {\n return this.globalStore.recurringTransactionsStore.incomeForRange(this.dateRange)\n }\n\n get upcomingIncome(): Recurrence[] {\n return this.globalStore.recurringTransactionsStore.upcomingIncomeForRange(this.dateRange)\n }\n\n get upcomingExpenses(): Recurrence[] {\n return this.globalStore.recurringTransactionsStore.upcomingExpensesForRange(this.dateRange)\n }\n\n get nextIncome(): Recurrence | undefined {\n return this.globalStore.recurringTransactionsStore.nextIncomeForRange(this.dateRange)\n }\n\n // Visible cash accounts that are currently selected in the account filter.\n // Computed (single source of truth) so it stays in sync with account data and\n // the filter without effect-based syncing in components.\n get selectedCashAccounts(): Account[] {\n const { selectedAccountGuids } = this.globalStore.globalUiStore\n return this.globalStore.accountStore.visibleCashAccounts.filter((account) =>\n selectedAccountGuids.includes(account.guid),\n )\n }\n\n // Total spendable/starting balance across the selected cash accounts, using\n // the shared per-account balance rule (available vs current balance).\n get selectedCashBalance(): number {\n const showAvailableBalance =\n this.globalStore.appConfig.config.display_available_balance_in_accounts\n return this.selectedCashAccounts.reduce(\n (total, account) => total + getAccountStartingBalance(account, showAvailableBalance),\n 0,\n )\n }\n // Returns the number of days until the next income activity or 0 if there is no income activity\n get daysUntilNextIncome() {\n return this.nextIncome\n ? Math.abs(differenceInDays(startOfToday(), this.nextIncome.expectedDate))\n : 0\n }\n\n // Returns the expenses (Activities) until the next income activity or the end of the month\n // if there is no income activity\n get expensesTilNextIncome() {\n return this.upcomingExpenses.filter((a) =>\n isBefore(a.expectedDate, this.nextIncome?.expectedDate || endOfMonth(new Date())),\n )\n }\n}\n"],"mappings":";;;;;;;AAYA,IAAa,IAAb,MAA2B;AAAA,EACzB;AAAA,EACA;AAAA,EAEA,YAAY,GAA0B;AACpC,SAAK,cAAc,GACnB,KAAK,UAAU,IAAI,EAAQ,CAAW,GAEtC,EAAmB,IAAI;AAAA,EACzB;AAAA,EAEA,qBAAqB,YAAY;AAC/B,UAAM,IAAqB;AAAA,MACzB,GAAG,KAAK,YAAY,UAAU;AAAA,MAC9B,oCAAoC;AAAA,IACtC;AAEA,UAAM,KAAK,YAAY,UAAU,kBAAkB,CAAkB;AAAA,EACvE;AACF,GAEM,IAAN,MAAc;AAAA,EACZ;AAAA,EAMA,YAAY;AAAA,IAAE,OAAO,EAAa;AAAA,IAAG,KAAK,EAAQ,EAAa,GAAG,EAAE;AAAA,EAAE;AAAA,EACtE,UAAU;AAAA,EAEV,YAAY,GAA0B;AACpC,SAAK,cAAc,GAEnB,EAAmB,IAAI;AAAA,EACzB;AAAA,EAKA,IAAI,SAAuB;AACzB,WAAO,KAAK,YAAY,2BAA2B,eAAe,KAAK,SAAS;AAAA,EAClF;AAAA,EAEA,IAAI,iBAA+B;AACjC,WAAO,KAAK,YAAY,2BAA2B,uBAAuB,KAAK,SAAS;AAAA,EAC1F;AAAA,EAEA,IAAI,mBAAiC;AACnC,WAAO,KAAK,YAAY,2BAA2B,yBAAyB,KAAK,SAAS;AAAA,EAC5F;AAAA,EAEA,IAAI,aAAqC;AACvC,WAAO,KAAK,YAAY,2BAA2B,mBAAmB,KAAK,SAAS;AAAA,EACtF;AAAA,EAKA,IAAI,uBAAkC;AACpC,UAAM,EAAE,sBAAA,EAAA,IAAyB,KAAK,YAAY;AAClD,WAAO,KAAK,YAAY,aAAa,oBAAoB,OAAA,CAAQ,MAC/D,EAAqB,SAAS,EAAQ,IAAI,CAC5C;AAAA,EACF;AAAA,EAIA,IAAI,sBAA8B;AAChC,UAAM,IACJ,KAAK,YAAY,UAAU,OAAO;AACpC,WAAO,KAAK,qBAAqB,OAAA,CAC9B,GAAO,MAAY,IAAQ,EAA0B,GAAS,CAAoB,GACnF,CACF;AAAA,EACF;AAAA,EAEA,IAAI,sBAAsB;AACxB,WAAO,KAAK,aACR,KAAK,IAAI,EAAiB,EAAa,GAAG,KAAK,WAAW,YAAY,CAAC,IACvE;AAAA,EACN;AAAA,EAIA,IAAI,wBAAwB;AAC1B,WAAO,KAAK,iBAAiB,OAAA,CAAQ,MACnC,EAAS,EAAE,cAAc,KAAK,YAAY,gBAAgB,EAAW,oBAAI,KAAK,CAAC,CAAC,CAClF;AAAA,EACF;AACF"}
1
+ {"version":3,"file":"CashflowStore.es.js","names":[],"sources":["../../../src/core/stores/CashflowStore.ts"],"sourcesContent":["import { makeAutoObservable } from 'mobx'\nimport { addDays } from 'date-fns/addDays'\nimport { differenceInDays } from 'date-fns/differenceInDays'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { isBefore } from 'date-fns/isBefore'\nimport { startOfToday } from 'date-fns/startOfToday'\n\nimport type { Account, Recurrence, UserProfile } from '../types'\nimport { getAccountStartingBalance } from '../utils/AccountUtils'\n\nimport { GlobalStore } from './GlobalStore'\n\nexport class CashflowStore {\n globalStore: GlobalStore\n uiStore: UiStore\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n this.uiStore = new UiStore(globalStore)\n\n makeAutoObservable(this)\n }\n\n completeOnboarding = async () => {\n const updatedUserProfile = {\n ...this.globalStore.userStore.userProfile,\n has_completed_cash_flow_onboarding: true,\n } as UserProfile\n\n await this.globalStore.userStore.updateUserProfile(updatedUserProfile)\n }\n}\n\nclass UiStore {\n globalStore: GlobalStore\n\n // Cashflow is hard-coded to display 30 days starting from today. This is the\n // single source of truth for Cash Flow's window; it intentionally does NOT\n // mutate RecurringTransactionsStore.dateRange, so Cash Flow stays decoupled\n // from other widgets (e.g. Recurring Transactions) that drive that shared range.\n dateRange = { start: startOfToday(), end: addDays(startOfToday(), 30) }\n isDirty = false\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n\n makeAutoObservable(this)\n }\n\n // Recurrence data scoped to Cash Flow's own dateRange, sourced from the\n // range-parameterized helpers on RecurringTransactionsStore. Computeds (not\n // component state) per the project's MobX conventions.\n get income(): Recurrence[] {\n return this.globalStore.recurringTransactionsStore.incomeForRange(this.dateRange)\n }\n\n get upcomingIncome(): Recurrence[] {\n return this.globalStore.recurringTransactionsStore.upcomingIncomeForRange(this.dateRange)\n }\n\n get upcomingExpenses(): Recurrence[] {\n return this.globalStore.recurringTransactionsStore.upcomingExpensesForRange(this.dateRange)\n }\n\n get nextIncome(): Recurrence | undefined {\n return this.globalStore.recurringTransactionsStore.nextIncomeForRange(this.dateRange)\n }\n\n // Visible cash accounts that are currently selected in the account filter.\n // Computed (single source of truth) so it stays in sync with account data and\n // the filter without effect-based syncing in components.\n get selectedCashAccounts(): Account[] {\n const { selectedAccountGuids } = this.globalStore.globalUiStore\n return this.globalStore.accountStore.visibleCashAccounts.filter((account) =>\n selectedAccountGuids.includes(account.guid),\n )\n }\n\n // Total spendable/starting balance across the selected cash accounts, using\n // the shared per-account balance rule (available vs current balance).\n get selectedCashBalance(): number {\n const showAvailableBalance =\n this.globalStore.appConfig.config.display_available_balance_in_accounts\n return this.selectedCashAccounts.reduce(\n (total, account) => total + getAccountStartingBalance(account, showAvailableBalance),\n 0,\n )\n }\n // Returns the number of days until the next income activity or 0 if there is no income activity\n get daysUntilNextIncome() {\n return this.nextIncome\n ? Math.abs(differenceInDays(startOfToday(), this.nextIncome.expectedDate))\n : 0\n }\n\n // Returns the expenses (Activities) until the next income activity or the end of the month\n // if there is no income activity\n get expensesTilNextIncome() {\n return this.upcomingExpenses.filter((a) =>\n isBefore(a.expectedDate, this.nextIncome?.expectedDate || endOfMonth(new Date())),\n )\n }\n\n // Sum of the expenses due before the next income (end of month when there is none).\n get upcomingExpensesTotal(): number {\n return this.expensesTilNextIncome.reduce((total, expense) => total + expense.amount, 0)\n }\n\n // Cash left once the upcoming expenses are paid; negative when short before payday.\n get remainingUntilPayday(): number {\n return this.selectedCashBalance - this.upcomingExpensesTotal\n }\n\n get hasUpcomingPayday(): boolean {\n return this.nextIncome !== undefined\n }\n}\n"],"mappings":";;;;;;;AAYA,IAAa,IAAb,MAA2B;AAAA,EACzB;AAAA,EACA;AAAA,EAEA,YAAY,GAA0B;AACpC,SAAK,cAAc,GACnB,KAAK,UAAU,IAAI,EAAQ,CAAW,GAEtC,EAAmB,IAAI;AAAA,EACzB;AAAA,EAEA,qBAAqB,YAAY;AAC/B,UAAM,IAAqB;AAAA,MACzB,GAAG,KAAK,YAAY,UAAU;AAAA,MAC9B,oCAAoC;AAAA,IACtC;AAEA,UAAM,KAAK,YAAY,UAAU,kBAAkB,CAAkB;AAAA,EACvE;AACF,GAEM,IAAN,MAAc;AAAA,EACZ;AAAA,EAMA,YAAY;AAAA,IAAE,OAAO,EAAa;AAAA,IAAG,KAAK,EAAQ,EAAa,GAAG,EAAE;AAAA,EAAE;AAAA,EACtE,UAAU;AAAA,EAEV,YAAY,GAA0B;AACpC,SAAK,cAAc,GAEnB,EAAmB,IAAI;AAAA,EACzB;AAAA,EAKA,IAAI,SAAuB;AACzB,WAAO,KAAK,YAAY,2BAA2B,eAAe,KAAK,SAAS;AAAA,EAClF;AAAA,EAEA,IAAI,iBAA+B;AACjC,WAAO,KAAK,YAAY,2BAA2B,uBAAuB,KAAK,SAAS;AAAA,EAC1F;AAAA,EAEA,IAAI,mBAAiC;AACnC,WAAO,KAAK,YAAY,2BAA2B,yBAAyB,KAAK,SAAS;AAAA,EAC5F;AAAA,EAEA,IAAI,aAAqC;AACvC,WAAO,KAAK,YAAY,2BAA2B,mBAAmB,KAAK,SAAS;AAAA,EACtF;AAAA,EAKA,IAAI,uBAAkC;AACpC,UAAM,EAAE,sBAAA,EAAA,IAAyB,KAAK,YAAY;AAClD,WAAO,KAAK,YAAY,aAAa,oBAAoB,OAAA,CAAQ,MAC/D,EAAqB,SAAS,EAAQ,IAAI,CAC5C;AAAA,EACF;AAAA,EAIA,IAAI,sBAA8B;AAChC,UAAM,IACJ,KAAK,YAAY,UAAU,OAAO;AACpC,WAAO,KAAK,qBAAqB,OAAA,CAC9B,GAAO,MAAY,IAAQ,EAA0B,GAAS,CAAoB,GACnF,CACF;AAAA,EACF;AAAA,EAEA,IAAI,sBAAsB;AACxB,WAAO,KAAK,aACR,KAAK,IAAI,EAAiB,EAAa,GAAG,KAAK,WAAW,YAAY,CAAC,IACvE;AAAA,EACN;AAAA,EAIA,IAAI,wBAAwB;AAC1B,WAAO,KAAK,iBAAiB,OAAA,CAAQ,MACnC,EAAS,EAAE,cAAc,KAAK,YAAY,gBAAgB,EAAW,oBAAI,KAAK,CAAC,CAAC,CAClF;AAAA,EACF;AAAA,EAGA,IAAI,wBAAgC;AAClC,WAAO,KAAK,sBAAsB,OAAA,CAAQ,GAAO,MAAY,IAAQ,EAAQ,QAAQ,CAAC;AAAA,EACxF;AAAA,EAGA,IAAI,uBAA+B;AACjC,WAAO,KAAK,sBAAsB,KAAK;AAAA,EACzC;AAAA,EAEA,IAAI,oBAA6B;AAC/B,WAAO,KAAK,eAAe;AAAA,EAC7B;AACF"}
@@ -161,6 +161,21 @@ export interface Description {
161
161
  text: string;
162
162
  html_text: string;
163
163
  }
164
+ /**
165
+ * Post message a CTA press sends to the host, resolved by the backend for the beat's current
166
+ * state. Absent/null = the backend has not described one: the frontend uses the template's
167
+ * interim `ctaPostMessages` entry if there is one, else sends no message. `payload` carries only
168
+ * beat-specific extras; the frontend always adds `beat_guid`, `beat_template`, `user_guid` and
169
+ * `action_string`.
170
+ */
171
+ export interface CtaPostMessageMetadata {
172
+ /** Post message event; defaults to `beat/cta`. */
173
+ event?: string | null;
174
+ /** Operation the host should perform, e.g. `op_1`, `op_2`, `op_3`. Defaults to `op_1`. */
175
+ action?: string | null;
176
+ /** Beat-specific fields merged into the message metadata (account ids, amounts, ...). */
177
+ payload?: Record<string, unknown> | null;
178
+ }
164
179
  export interface PrimaryCTA {
165
180
  label: string;
166
181
  block: string;
@@ -171,6 +186,7 @@ export interface PrimaryCTA {
171
186
  * null / absent / unknown means the CTA only opens its block or posts its message.
172
187
  */
173
188
  action_key?: string | null;
189
+ metadata?: CtaPostMessageMetadata | null;
174
190
  }
175
191
  export interface SecondaryCTA {
176
192
  label: string;
@@ -178,6 +194,7 @@ export interface SecondaryCTA {
178
194
  variant?: string | null;
179
195
  /** See `PrimaryCTA.action_key` (ditto `secondary_cta_action`). */
180
196
  action_key?: string | null;
197
+ metadata?: CtaPostMessageMetadata | null;
181
198
  }
182
199
  export interface Footer {
183
200
  /** V1: a pre-formatted date string. */
@@ -13,7 +13,7 @@ export type { Expense } from './Expense';
13
13
  export { ExpenseStatus, ScheduledPaymentTypes } from './Expense';
14
14
  export type { DynamicCopyElement, GlobalCopy } from './GlobalCopy';
15
15
  export { GoalType, MetaType, TrackType } from './Goal';
16
- export type { ThemedSVGImageProps, DrilldownData, DrilldownPayload, InsightsPayload, InstanceSlot, Pill, WidgetType, InstanceSlotRow, BlockType, Description, PrimaryCTA, SecondaryCTA, Subtitle, Footer, Logo, LogoRef, CategoryLogo, MerchantLogo, InstitutionLogo, Legends, } from './Insights';
16
+ export type { ThemedSVGImageProps, DrilldownData, DrilldownPayload, InsightsPayload, InstanceSlot, Pill, WidgetType, InstanceSlotRow, BlockType, Description, PrimaryCTA, CtaPostMessageMetadata, SecondaryCTA, Subtitle, Footer, Logo, LogoRef, CategoryLogo, MerchantLogo, InstitutionLogo, Legends, } from './Insights';
17
17
  export type { Goal, MonthlyCashFlowProfile, RetirementGoalAccount } from './Goal';
18
18
  export type { Debt } from './Debt';
19
19
  export type { GlobalCaptureException, GlobalErrorHandler, GlobalEventHandler } from './Handlers';
@@ -132,10 +132,25 @@ export interface FeedbackCopy {
132
132
  /** ARIA label for the close button. */
133
133
  close_aria: string;
134
134
  }
135
+ export interface RemainingCashCopy {
136
+ title: string;
137
+ remaining_until_payday: string;
138
+ remaining_until_payday_one: string;
139
+ cash_on_hand: string;
140
+ upcoming_expenses: string;
141
+ accounts_header: string;
142
+ recurring_expenses_header: string;
143
+ other_accounts: string;
144
+ other_accounts_one: string;
145
+ other_expenses: string;
146
+ other_expenses_one: string;
147
+ }
135
148
  export interface InsightsFeedCopy {
136
149
  general: GeneralInsightsFeedCopy;
137
150
  settings_menu: SettingsMenuCopy;
138
151
  make_transfer: MakeTransferCopy;
152
+ /** Optional until the localized-content service ships it; the drawer shows an error without it. */
153
+ remaining_cash?: RemainingCashCopy;
139
154
  overflow_menu: OverflowMenuCopy;
140
155
  transaction_history_block: TransactionHistoryBlock;
141
156
  hidden_confirmation: HiddenConfirmationCopy;
@@ -13,7 +13,7 @@ export type { DebtsCopy } from './DebtsCopy';
13
13
  export type { FinstrongCopy } from './FinstrongCopy';
14
14
  export type { GoalsCopy } from './GoalsCopy';
15
15
  export type { HelpCopy, HelpByCategoryList } from './HelpCopy';
16
- export type { InsightsFeedCopy, MakeTransferCopy, OverflowMenuCopy, DismissInsightCopy, DismissInsightOptionCopy, HiddenConfirmationCopy, HideFeedbackCopy, FeedbackCopy, FeedbackBarCopy, } from './InsightsFeedCopy';
16
+ export type { InsightsFeedCopy, MakeTransferCopy, RemainingCashCopy, OverflowMenuCopy, DismissInsightCopy, DismissInsightOptionCopy, HiddenConfirmationCopy, HideFeedbackCopy, FeedbackCopy, FeedbackBarCopy, } from './InsightsFeedCopy';
17
17
  export type { InvestmentsCopy } from './InvestmentsCopy';
18
18
  export type { MicroInsightsCopy } from './MicroInsightsCopy';
19
19
  export type { NetWorthCopy } from './NetWorthCopy';
@@ -1,2 +1,2 @@
1
1
  export { RecurringTransactionBlock } from './RecurringTransactionBlock';
2
- export { useRecurringTransactionPrepare } from './useRecurringTransactionPrepare';
2
+ export { type RecurringTransactionActionContext, useRecurringTransactionLoad, useRecurringTransactionPrepare, } from './useRecurringTransactionPrepare';
@@ -0,0 +1,11 @@
1
+ import { ActionBlockProps } from '../../mappings/blocks';
2
+ /**
3
+ * Remaining Cash drawer (WEC-1880) for the Payday Cash Left Over Status beat. Store-driven: the
4
+ * figures are today's, from CashflowStore's UiStore, not the card's generation-time numbers
5
+ * (accepted trade-off, see docs/action-blocks/remaining-cash-block.md).
6
+ *
7
+ * `observer` is required: everything rendered is read from MobX stores and computeds.
8
+ */
9
+ export declare const RemainingCashBlock: ((_props: ActionBlockProps) => import("react/jsx-runtime").JSX.Element) & {
10
+ displayName: string;
11
+ };
@@ -0,0 +1,89 @@
1
+ import { CategoryGuids as T } from "../../../core/constants/Category.es.js";
2
+ import R from "../../../common/utils/AccountDisplayName.es.js";
3
+ import { useAccountStore as G, useActionContext as S, useAppConfig as w, useCashflowStore as A, useCategoryStore as I, useGlobalCopyStore as U, useRecurringTransactionsStore as v, useTransactionStore as E } from "../../../common/context/hooks.es.js";
4
+ import { getAccountStartingBalance as O } from "../../../core/utils/AccountUtils.es.js";
5
+ import b from "../../../common/components/Loader.es.js";
6
+ import { ErrorCard as k } from "../../components/ErrorCard.es.js";
7
+ import { useRecurringTransactionLoad as N } from "../RecurringTransactionBlock/useRecurringTransactionPrepare.es.js";
8
+ import { DEFAULT_INSTITUTION_LOGO_URL as D, getCaption as F, getOtherLabel as z, groupRows as B } from "./layout.es.js";
9
+ import H from "./RemainingCashContent.es.js";
10
+ import { observer as P } from "mobx-react-lite";
11
+ import $ from "@mui/material/Stack";
12
+ import { CategoryIcon as d, InstitutionLogo as g, Text as j } from "@mxenabled/mxui";
13
+ import { jsx as n } from "react/jsx-runtime";
14
+ var p = (m, i, s) => {
15
+ const { others: a, shown: r } = B(m);
16
+ return a ? [...r, {
17
+ amount: a.total,
18
+ key: "other",
19
+ leading: s,
20
+ name: z(i.one, i.multi, a.count)
21
+ }] : r;
22
+ }, to = P(function(i) {
23
+ const { common: s, insights_feed: a } = U(), { config: r } = w(), { uiStore: t } = A(), { getTopLevelCategoryGuid: l } = I(), { accountsLoaded: h } = G(), { transactionsLoaded: u, transactionsLoading: y } = E(), { isDataLoaded: c } = v(), _ = S()?.recurringLoadFailed === !0, f = N(), C = c && !u && !y, o = a?.remaining_cash;
24
+ if (a?.general && !o) return /* @__PURE__ */ n($, {
25
+ sx: { p: 24 },
26
+ children: /* @__PURE__ */ n(j, { children: s?.error_message })
27
+ });
28
+ if (_ && !c || C) return /* @__PURE__ */ n(k, { onRetry: () => {
29
+ f();
30
+ } });
31
+ if (!c || !h || !u || !o) return /* @__PURE__ */ n(b, {});
32
+ const L = p(t.selectedCashAccounts.map((e) => ({
33
+ amount: O(e, r.display_available_balance_in_accounts),
34
+ key: e.guid,
35
+ leading: /* @__PURE__ */ n(g, {
36
+ alt: "",
37
+ institutionGuid: e.institution_guid ?? "",
38
+ size: 32
39
+ }),
40
+ name: R(e)
41
+ })), {
42
+ multi: o.other_accounts,
43
+ one: o.other_accounts_one
44
+ }, /* @__PURE__ */ n(g, {
45
+ alt: "",
46
+ institutionGuid: "",
47
+ logoUrl: D,
48
+ size: 32
49
+ })), x = p(t.expensesTilNextIncome.map((e) => ({
50
+ amount: e.amount,
51
+ key: `${e.repeatingTransactionGuid}-${e.expectedDate.getTime()}`,
52
+ leading: /* @__PURE__ */ n(d, {
53
+ categoryGuid: l(e.categoryGuid),
54
+ size: 32
55
+ }),
56
+ name: e.name
57
+ })), {
58
+ multi: o.other_expenses,
59
+ one: o.other_expenses_one
60
+ }, /* @__PURE__ */ n(d, {
61
+ categoryGuid: T.UNCATEGORIZED,
62
+ size: 32
63
+ }));
64
+ return /* @__PURE__ */ n(H, {
65
+ accounts: {
66
+ header: o.accounts_header,
67
+ rows: L
68
+ },
69
+ expenses: {
70
+ header: o.recurring_expenses_header,
71
+ rows: x
72
+ },
73
+ summary: {
74
+ remaining: t.hasUpcomingPayday ? {
75
+ amount: t.remainingUntilPayday,
76
+ caption: F(o, t.daysUntilNextIncome)
77
+ } : void 0,
78
+ cashOnHand: t.selectedCashBalance,
79
+ cashOnHandLabel: o.cash_on_hand,
80
+ upcomingExpenses: t.upcomingExpensesTotal,
81
+ upcomingExpensesLabel: o.upcoming_expenses
82
+ }
83
+ });
84
+ });
85
+ export {
86
+ to as RemainingCashBlock
87
+ };
88
+
89
+ //# sourceMappingURL=RemainingCashBlock.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemainingCashBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashBlock.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\n\nimport Stack from '@mui/material/Stack'\n\nimport { CategoryIcon, InstitutionLogo, Text } from '@mxenabled/mxui'\nimport { observer } from 'mobx-react-lite'\n\nimport {\n Loader,\n useAccountStore,\n useActionContext,\n useAppConfig,\n useCashflowStore,\n useCategoryStore,\n useGlobalCopyStore,\n useRecurringTransactionsStore,\n useTransactionStore,\n} from '../../../common'\nimport getAccountDisplayName from '../../../common/utils/AccountDisplayName'\nimport { CategoryGuids } from '../../../core/constants/Category'\nimport { getAccountStartingBalance } from '../../../core/utils/AccountUtils'\nimport { ErrorCard } from '../../components/ErrorCard'\nimport type { ActionBlockProps } from '../../mappings/blocks'\nimport {\n type RecurringTransactionActionContext,\n useRecurringTransactionLoad,\n} from '../RecurringTransactionBlock'\n\nimport { DEFAULT_INSTITUTION_LOGO_URL, getCaption, getOtherLabel, groupRows } from './layout'\nimport RemainingCashContent from './RemainingCashContent'\nimport type { RemainingCashRow } from './RemainingCashContent'\n\n/** Top rows by amount, then one \"Other (#)\" row with the rest's count and total. */\nconst toSectionRows = (\n rows: RemainingCashRow[],\n otherLabel: { one: string; multi: string },\n otherLeading: ReactNode,\n): RemainingCashRow[] => {\n const { others, shown } = groupRows(rows)\n\n if (!others) return shown\n\n return [\n ...shown,\n {\n amount: others.total,\n key: 'other',\n leading: otherLeading,\n name: getOtherLabel(otherLabel.one, otherLabel.multi, others.count),\n },\n ]\n}\n\n/**\n * Remaining Cash drawer (WEC-1880) for the Payday Cash Left Over Status beat. Store-driven: the\n * figures are today's, from CashflowStore's UiStore, not the card's generation-time numbers\n * (accepted trade-off, see docs/action-blocks/remaining-cash-block.md).\n *\n * `observer` is required: everything rendered is read from MobX stores and computeds.\n */\nexport const RemainingCashBlock = observer(function RemainingCashBlock(_props: ActionBlockProps) {\n const { common: commonCopy, insights_feed: insightsFeedCopy } = useGlobalCopyStore()\n const { config } = useAppConfig()\n const { uiStore } = useCashflowStore()\n const { getTopLevelCategoryGuid } = useCategoryStore()\n const { accountsLoaded } = useAccountStore()\n const { transactionsLoaded, transactionsLoading } = useTransactionStore()\n const { isDataLoaded } = useRecurringTransactionsStore()\n const loadFailed =\n useActionContext<RecurringTransactionActionContext>()?.recurringLoadFailed === true\n const retryLoad = useRecurringTransactionLoad()\n\n // TransactionStore swallows a failed request, which would silently zero every expense (see the\n // States table in docs/action-blocks/remaining-cash-block.md). `transactionsLoading` excludes another\n // widget's request still in flight: loadTransactions returns early without awaiting it.\n const transactionsFailed = isDataLoaded && !transactionsLoaded && !transactionsLoading\n const copy = insightsFeedCopy?.remaining_cash\n\n // Copy loaded but without this namespace (the copy service has not shipped it). Copy loads once at\n // app start, so a retry cannot clear it: a plain message like ActionBlockLayer's unknown-block\n // state, not ErrorCard's Try Again, and never an endless loader.\n if (insightsFeedCopy?.general && !copy) {\n return (\n <Stack sx={{ p: 24 }}>\n <Text>{commonCopy?.error_message}</Text>\n </Stack>\n )\n }\n\n if ((loadFailed && !isDataLoaded) || transactionsFailed) {\n return <ErrorCard onRetry={() => void retryLoad()} />\n }\n\n // `accountsLoaded` / `transactionsLoaded` cover another widget's request still in flight.\n if (!isDataLoaded || !accountsLoaded || !transactionsLoaded || !copy) return <Loader />\n\n const accountRows = toSectionRows(\n uiStore.selectedCashAccounts.map((account) => ({\n amount: getAccountStartingBalance(account, config.display_available_balance_in_accounts),\n key: account.guid,\n // Decorative: the row's visible name already says which account it is.\n leading: (\n <InstitutionLogo alt=\"\" institutionGuid={account.institution_guid ?? ''} size={32} />\n ),\n name: getAccountDisplayName(account),\n })),\n { multi: copy.other_accounts, one: copy.other_accounts_one },\n <InstitutionLogo alt=\"\" institutionGuid=\"\" logoUrl={DEFAULT_INSTITUTION_LOGO_URL} size={32} />,\n )\n const expenseRows = toSectionRows(\n uiStore.expensesTilNextIncome.map((expense) => ({\n amount: expense.amount,\n key: `${expense.repeatingTransactionGuid}-${expense.expectedDate.getTime()}`,\n leading: (\n <CategoryIcon categoryGuid={getTopLevelCategoryGuid(expense.categoryGuid)} size={32} />\n ),\n name: expense.name,\n })),\n { multi: copy.other_expenses, one: copy.other_expenses_one },\n <CategoryIcon categoryGuid={CategoryGuids.UNCATEGORIZED} size={32} />,\n )\n\n return (\n <RemainingCashContent\n accounts={{ header: copy.accounts_header, rows: accountRows }}\n expenses={{ header: copy.recurring_expenses_header, rows: expenseRows }}\n summary={{\n remaining: uiStore.hasUpcomingPayday\n ? {\n amount: uiStore.remainingUntilPayday,\n caption: getCaption(copy, uiStore.daysUntilNextIncome),\n }\n : undefined,\n cashOnHand: uiStore.selectedCashBalance,\n cashOnHandLabel: copy.cash_on_hand,\n upcomingExpenses: uiStore.upcomingExpensesTotal,\n upcomingExpensesLabel: copy.upcoming_expenses,\n }}\n />\n )\n})\n"],"mappings":";;;;;;;;;;;;;AAiCA,IAAM,IAAA,CACJ,GACA,GACA,MACuB;AACvB,QAAM,EAAE,QAAA,GAAQ,OAAA,EAAA,IAAU,EAAU,CAAI;AAExC,SAAK,IAEE,CACL,GAAG,GACH;AAAA,IACE,QAAQ,EAAO;AAAA,IACf,KAAK;AAAA,IACL,SAAS;AAAA,IACT,MAAM,EAAc,EAAW,KAAK,EAAW,OAAO,EAAO,KAAK;AAAA,EACpE,CACF,IAVoB;AAWtB,GASa,KAAqB,EAAS,SAA4B,GAA0B;AAC/F,QAAM,EAAE,QAAQ,GAAY,eAAe,EAAA,IAAqB,EAAmB,GAC7E,EAAE,QAAA,EAAA,IAAW,EAAa,GAC1B,EAAE,SAAA,EAAA,IAAY,EAAiB,GAC/B,EAAE,yBAAA,EAAA,IAA4B,EAAiB,GAC/C,EAAE,gBAAA,EAAA,IAAmB,EAAgB,GACrC,EAAE,oBAAA,GAAoB,qBAAA,EAAA,IAAwB,EAAoB,GAClE,EAAE,cAAA,EAAA,IAAiB,EAA8B,GACjD,IACJ,EAAoD,GAAG,wBAAwB,IAC3E,IAAY,EAA4B,GAKxC,IAAqB,KAAgB,CAAC,KAAsB,CAAC,GAC7D,IAAO,GAAkB;AAK/B,MAAI,GAAkB,WAAW,CAAC,EAChC,QACE,gBAAA,EAAC,GAAD;AAAA,IAAO,IAAI,EAAE,GAAG,GAAG;AAAA,IACjB,UAAA,gBAAA,EAAC,GAAD,EAAA,UAAO,GAAY,cAAoB,CAAA;AAAA,EAClC,CAAA;AAIX,MAAK,KAAc,CAAC,KAAiB,EACnC,QAAO,gBAAA,EAAC,GAAD,EAAW,SAAA,MAAA;AAAe,IAAK,EAAU;AAAA,IAAI,CAAA;AAItD,MAAI,CAAC,KAAgB,CAAC,KAAkB,CAAC,KAAsB,CAAC,EAAM,QAAO,gBAAA,EAAC,GAAD,CAAS,CAAA;AAEtF,QAAM,IAAc,EAClB,EAAQ,qBAAqB,IAAA,CAAK,OAAa;AAAA,IAC7C,QAAQ,EAA0B,GAAS,EAAO,qCAAqC;AAAA,IACvF,KAAK,EAAQ;AAAA,IAEb,SACE,gBAAA,EAAC,GAAD;AAAA,MAAiB,KAAI;AAAA,MAAG,iBAAiB,EAAQ,oBAAoB;AAAA,MAAI,MAAM;AAAA,IAAK,CAAA;AAAA,IAEtF,MAAM,EAAsB,CAAO;AAAA,EACrC,EAAE,GACF;AAAA,IAAE,OAAO,EAAK;AAAA,IAAgB,KAAK,EAAK;AAAA,EAAmB,GAC3D,gBAAA,EAAC,GAAD;AAAA,IAAiB,KAAI;AAAA,IAAG,iBAAgB;AAAA,IAAG,SAAS;AAAA,IAA8B,MAAM;AAAA,EAAK,CAAA,CAC/F,GACM,IAAc,EAClB,EAAQ,sBAAsB,IAAA,CAAK,OAAa;AAAA,IAC9C,QAAQ,EAAQ;AAAA,IAChB,KAAK,GAAG,EAAQ,wBAAA,IAA4B,EAAQ,aAAa,QAAQ,CAAA;AAAA,IACzE,SACE,gBAAA,EAAC,GAAD;AAAA,MAAc,cAAc,EAAwB,EAAQ,YAAY;AAAA,MAAG,MAAM;AAAA,IAAK,CAAA;AAAA,IAExF,MAAM,EAAQ;AAAA,EAChB,EAAE,GACF;AAAA,IAAE,OAAO,EAAK;AAAA,IAAgB,KAAK,EAAK;AAAA,EAAmB,GAC3D,gBAAA,EAAC,GAAD;AAAA,IAAc,cAAc,EAAc;AAAA,IAAe,MAAM;AAAA,EAAK,CAAA,CACtE;AAEA,SACE,gBAAA,EAAC,GAAD;AAAA,IACE,UAAU;AAAA,MAAE,QAAQ,EAAK;AAAA,MAAiB,MAAM;AAAA,IAAY;AAAA,IAC5D,UAAU;AAAA,MAAE,QAAQ,EAAK;AAAA,MAA2B,MAAM;AAAA,IAAY;AAAA,IACtE,SAAS;AAAA,MACP,WAAW,EAAQ,oBACf;AAAA,QACE,QAAQ,EAAQ;AAAA,QAChB,SAAS,EAAW,GAAM,EAAQ,mBAAmB;AAAA,MACvD,IACA;AAAA,MACJ,YAAY,EAAQ;AAAA,MACpB,iBAAiB,EAAK;AAAA,MACtB,kBAAkB,EAAQ;AAAA,MAC1B,uBAAuB,EAAK;AAAA,IAC9B;AAAA,EACD,CAAA;AAEL,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { ReactNode } from 'react';
2
+ export interface RemainingCashRow {
3
+ key: string;
4
+ name: string;
5
+ amount: number;
6
+ /** The row's 32px logo: an account's institution or an expense's category. */
7
+ leading: ReactNode;
8
+ }
9
+ interface RemainingCashSectionProps {
10
+ header: string;
11
+ /** Already grouped and ordered, including any "Other (#)" row. */
12
+ rows: RemainingCashRow[];
13
+ }
14
+ interface RemainingCashSummaryProps {
15
+ /** Omitted when there is no upcoming payday: a "remaining until payday" figure has no meaning. */
16
+ remaining?: {
17
+ amount: number;
18
+ caption: string;
19
+ };
20
+ cashOnHand: number;
21
+ upcomingExpenses: number;
22
+ cashOnHandLabel: string;
23
+ upcomingExpensesLabel: string;
24
+ }
25
+ export interface RemainingCashContentProps {
26
+ summary: RemainingCashSummaryProps;
27
+ accounts: RemainingCashSectionProps;
28
+ expenses: RemainingCashSectionProps;
29
+ }
30
+ /** Presentational body of the Remaining Cash drawer (Figma 9936:39163, minus the drawer header). */
31
+ declare const RemainingCashContent: ({ accounts, expenses, summary }: RemainingCashContentProps) => import("react/jsx-runtime").JSX.Element;
32
+ export default RemainingCashContent;
@@ -0,0 +1,169 @@
1
+ import { getLogoElevation as x } from "../../components/logoShadow.es.js";
2
+ import { formatSignedCurrency as d } from "./layout.es.js";
3
+ import { createElement as b } from "react";
4
+ import i from "@mui/material/Stack";
5
+ import { Text as a } from "@mxenabled/mxui";
6
+ import { jsx as r, jsxs as n } from "react/jsx-runtime";
7
+ import p from "@mui/material/Box";
8
+ import s from "@mui/material/Divider";
9
+ var h = ({ label: t, value: e }) => /* @__PURE__ */ n(i, {
10
+ sx: {
11
+ alignItems: "center",
12
+ flexDirection: "row",
13
+ gap: 16,
14
+ minHeight: 44,
15
+ px: 24
16
+ },
17
+ children: [/* @__PURE__ */ r(a, {
18
+ sx: { flexGrow: 1 },
19
+ variant: "body1",
20
+ children: t
21
+ }), /* @__PURE__ */ r(a, {
22
+ bold: !0,
23
+ sx: { fontVariantNumeric: "tabular-nums" },
24
+ variant: "body1",
25
+ children: e
26
+ })]
27
+ }), u = ({ cashOnHand: t, cashOnHandLabel: e, remaining: o, upcomingExpenses: l, upcomingExpensesLabel: c }) => /* @__PURE__ */ r(i, {
28
+ sx: {
29
+ pb: 8,
30
+ pt: 24,
31
+ px: 24
32
+ },
33
+ children: /* @__PURE__ */ n(i, {
34
+ sx: {
35
+ bgcolor: "background.paper",
36
+ borderRadius: "8px",
37
+ boxShadow: x,
38
+ gap: 16,
39
+ p: 16
40
+ },
41
+ children: [o && /* @__PURE__ */ n(i, {
42
+ "aria-label": `${d(o.amount)}, ${o.caption}`,
43
+ role: "group",
44
+ sx: {
45
+ alignItems: "center",
46
+ justifyContent: "center",
47
+ minHeight: 72,
48
+ p: 12
49
+ },
50
+ children: [/* @__PURE__ */ r(a, {
51
+ "aria-hidden": !0,
52
+ bold: !0,
53
+ component: "p",
54
+ variant: "h2",
55
+ children: d(o.amount)
56
+ }), /* @__PURE__ */ r(a, {
57
+ "aria-hidden": !0,
58
+ color: "text.secondary",
59
+ variant: "subtitle2",
60
+ children: o.caption
61
+ })]
62
+ }), /* @__PURE__ */ n(i, {
63
+ sx: {
64
+ border: "1px solid",
65
+ borderColor: "divider",
66
+ borderRadius: "8px",
67
+ overflow: "hidden"
68
+ },
69
+ children: [
70
+ /* @__PURE__ */ r(h, {
71
+ label: e,
72
+ value: d(t)
73
+ }),
74
+ /* @__PURE__ */ r(s, {}),
75
+ /* @__PURE__ */ r(h, {
76
+ label: c,
77
+ value: d(-l)
78
+ })
79
+ ]
80
+ })]
81
+ })
82
+ }), f = ({ amount: t, isLast: e, leading: o, name: l }) => /* @__PURE__ */ n(p, {
83
+ component: "li",
84
+ sx: { position: "relative" },
85
+ children: [/* @__PURE__ */ n(i, {
86
+ sx: {
87
+ alignItems: "center",
88
+ flexDirection: "row",
89
+ gap: 16,
90
+ px: 24,
91
+ py: 14
92
+ },
93
+ children: [/* @__PURE__ */ n(i, {
94
+ sx: {
95
+ alignItems: "center",
96
+ flexDirection: "row",
97
+ flexGrow: 1,
98
+ gap: 12,
99
+ minWidth: 0
100
+ },
101
+ children: [/* @__PURE__ */ r(p, {
102
+ sx: {
103
+ display: "flex",
104
+ flexShrink: 0,
105
+ height: 32,
106
+ width: 32
107
+ },
108
+ children: o
109
+ }), /* @__PURE__ */ r(a, {
110
+ truncate: !0,
111
+ variant: "body1",
112
+ children: l
113
+ })]
114
+ }), /* @__PURE__ */ r(a, {
115
+ bold: !0,
116
+ sx: {
117
+ flexShrink: 0,
118
+ fontVariantNumeric: "tabular-nums"
119
+ },
120
+ variant: "body1",
121
+ children: d(t)
122
+ })]
123
+ }), /* @__PURE__ */ r(s, { sx: {
124
+ bottom: 0,
125
+ left: e ? 0 : 24,
126
+ position: "absolute",
127
+ right: 0
128
+ } })]
129
+ }), m = ({ header: t, rows: e }) => /* @__PURE__ */ n(i, { children: [/* @__PURE__ */ r(p, {
130
+ sx: {
131
+ pb: 4,
132
+ pt: 16,
133
+ px: 24
134
+ },
135
+ children: /* @__PURE__ */ r(a, {
136
+ bold: !0,
137
+ component: "h3",
138
+ variant: "body1",
139
+ children: t
140
+ })
141
+ }), /* @__PURE__ */ r(i, {
142
+ component: "ul",
143
+ sx: {
144
+ bgcolor: "background.paper",
145
+ listStyle: "none",
146
+ m: 0,
147
+ p: 0
148
+ },
149
+ children: e.map(({ key: o, ...l }, c) => /* @__PURE__ */ b(f, {
150
+ ...l,
151
+ isLast: c === e.length - 1,
152
+ key: o
153
+ }))
154
+ })] }), R = ({ accounts: t, expenses: e, summary: o }) => /* @__PURE__ */ n(i, {
155
+ sx: {
156
+ bgcolor: "background.default",
157
+ pb: 24
158
+ },
159
+ children: [
160
+ /* @__PURE__ */ r(u, { ...o }),
161
+ t.rows.length > 0 && /* @__PURE__ */ r(m, { ...t }),
162
+ e.rows.length > 0 && /* @__PURE__ */ r(m, { ...e })
163
+ ]
164
+ });
165
+ export {
166
+ R as default
167
+ };
168
+
169
+ //# sourceMappingURL=RemainingCashContent.es.js.map