@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.
- package/CHANGELOG.md +13 -0
- package/dist/cashflow/context/hooks.d.ts +3 -0
- package/dist/common/utils/PostMessage.d.ts +5 -1
- package/dist/common/utils/PostMessage.es.js +26 -24
- package/dist/common/utils/PostMessage.es.js.map +1 -1
- package/dist/core/stores/CashflowStore.d.ts +3 -0
- package/dist/core/stores/CashflowStore.es.js +9 -0
- package/dist/core/stores/CashflowStore.es.js.map +1 -1
- package/dist/core/types/Insights.d.ts +17 -0
- package/dist/core/types/index.d.ts +1 -1
- package/dist/core/types/localization/InsightsFeedCopy.d.ts +15 -0
- package/dist/core/types/localization/index.d.ts +1 -1
- package/dist/insights-v2/actionBlocks/RecurringTransactionBlock/index.d.ts +1 -1
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashBlock.d.ts +11 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashBlock.es.js +89 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashBlock.es.js.map +1 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashContent.d.ts +32 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashContent.es.js +169 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/RemainingCashContent.es.js.map +1 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/index.d.ts +1 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/layout.d.ts +27 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/layout.es.js +26 -0
- package/dist/insights-v2/actionBlocks/RemainingCashBlock/layout.es.js.map +1 -0
- package/dist/insights-v2/components/BaseLayout.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/ctaConfig.d.ts +3 -2
- package/dist/insights-v2/ctaActions/ctaConfig.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/ctaPostMessages.d.ts +16 -0
- package/dist/insights-v2/ctaActions/ctaPostMessages.es.js +51 -0
- package/dist/insights-v2/ctaActions/ctaPostMessages.es.js.map +1 -0
- package/dist/insights-v2/ctaActions/index.d.ts +1 -0
- package/dist/insights-v2/ctaActions/runCtaAction.d.ts +5 -1
- package/dist/insights-v2/ctaActions/runCtaAction.es.js +54 -51
- package/dist/insights-v2/ctaActions/runCtaAction.es.js.map +1 -1
- package/dist/insights-v2/ctaActions/types.d.ts +1 -1
- package/dist/insights-v2/mappings/actionBlocksKey.d.ts +1 -0
- package/dist/insights-v2/mappings/actionBlocksKey.es.js +2 -1
- package/dist/insights-v2/mappings/actionBlocksKey.es.js.map +1 -1
- package/dist/insights-v2/mappings/blocks.es.js +48 -42
- package/dist/insights-v2/mappings/blocks.es.js.map +1 -1
- 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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
23
|
-
function
|
|
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), [
|
|
29
|
+
return e?.payload?.action_string && (t.action_string = e.payload.action_string), [l, t];
|
|
30
30
|
}
|
|
31
|
-
function
|
|
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
|
|
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
|
|
43
|
-
const [
|
|
44
|
-
...
|
|
45
|
-
...o ? { user_guid: o } : {}
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 [
|
|
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
|