@mx-cartographer/experiences 9.7.106 → 9.7.107
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 +5 -0
- package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js +16 -11
- package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js.map +1 -1
- package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/layout.d.ts +1 -0
- package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/layout.es.js +116 -100
- package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/layout.es.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## [9.7.107] - 11-05-2026
|
|
2
|
+
|
|
3
|
+
- **FIXED** - The Savings Account Balance Summary "Savings Contributions" drawer reads its bars, trend and accounts from the beat's `drilldown_data` (`resolveSavingsContributionsSlotData`) instead of `instance_slot.data` and the beat, and fails closed: a beat whose `drilldown_data` names no savings account lists no transactions instead of every account (WEC-2023)
|
|
4
|
+
- **FIXED** - A Transaction History chart drawer with no bars lists no transactions instead of keeping the store's default (every account) or a previous drawer's filter (WEC-2023)
|
|
5
|
+
|
|
1
6
|
## [9.7.106] - 11-05-2026
|
|
2
7
|
|
|
3
8
|
- **ADDED** - Micro Deposits on Insights 2.0: the deposits as green credits, and a Deposit History drawer (WEC-2109)
|
|
@@ -19,7 +19,7 @@ import { observer as Pt } from "mobx-react-lite";
|
|
|
19
19
|
import { Fragment as Vt, jsx as r, jsxs as zt } from "react/jsx-runtime";
|
|
20
20
|
import { useTheme as Ut } from "@mui/material/styles";
|
|
21
21
|
var pe = Pt(({ beat: s }) => {
|
|
22
|
-
const D = Ut(), { actionDrawer: B, setActionContext: S } = yt(), { insights_payload: c } = s, V = Rt(B, c?.primary_cta, c?.secondary_cta, c?.in_description_cta)?.variant, { top: e, filterMode: u, header: z, colors: U, filterStrategy: W, tipSection: L, subTabAriaLabels: Y, slotData: $, beatTransactions: q, hideListHeader: J } = Ft(V ?? ""), { instanceSlotData: a, beatTransactions: g, spendCategoryGuids: v, allAccountGuids: I, categoryName: Q, displayedDateRange:
|
|
22
|
+
const D = Ut(), { actionDrawer: B, setActionContext: S } = yt(), { insights_payload: c } = s, V = Rt(B, c?.primary_cta, c?.secondary_cta, c?.in_description_cta)?.variant, { top: e, filterMode: u, header: z, colors: U, filterStrategy: W, tipSection: L, subTabAriaLabels: Y, slotData: $, beatTransactions: q, hideListHeader: J } = Ft(V ?? ""), { instanceSlotData: a, beatTransactions: g, spendCategoryGuids: v, allAccountGuids: I, categoryName: Q, displayedDateRange: h, transactionStore: X } = Nt(s, {
|
|
23
23
|
slotData: $,
|
|
24
24
|
beatTransactions: q
|
|
25
25
|
}), { common: Z, insights_feed: n } = Tt(), { isCopyLoaded: tt } = Ct(), [et, x] = m(!1), [ot, at] = m(!1), E = It(), { dataSeries: p, chartData: b, comparisonBars: i } = Gt({
|
|
@@ -44,11 +44,16 @@ var pe = Pt(({ beat: s }) => {
|
|
|
44
44
|
isLastTab: t === l.length - 1,
|
|
45
45
|
instanceSlotData: a,
|
|
46
46
|
spendCategoryGuids: v,
|
|
47
|
-
displayedDateRange:
|
|
47
|
+
displayedDateRange: h
|
|
48
48
|
}), lt = g.map((t) => t.guid).join(","), ct = v.join(","), dt = I.join(","), mt = e === "list" || e === "tabsOnly" || e === "monthTabs" || p.length > 0, j = () => {
|
|
49
|
-
u !== "store"
|
|
49
|
+
if (u !== "store") return;
|
|
50
|
+
const t = mt ? C(o) : {
|
|
51
|
+
accounts: [],
|
|
52
|
+
custom: () => !1
|
|
53
|
+
};
|
|
54
|
+
X.setFilter(kt(t, {
|
|
50
55
|
allAccountGuids: I,
|
|
51
|
-
displayedDateRange:
|
|
56
|
+
displayedDateRange: h
|
|
52
57
|
}));
|
|
53
58
|
};
|
|
54
59
|
Mt(() => {
|
|
@@ -60,7 +65,7 @@ var pe = Pt(({ beat: s }) => {
|
|
|
60
65
|
u,
|
|
61
66
|
p.length,
|
|
62
67
|
a,
|
|
63
|
-
|
|
68
|
+
h,
|
|
64
69
|
ct,
|
|
65
70
|
dt,
|
|
66
71
|
lt,
|
|
@@ -76,9 +81,9 @@ var pe = Pt(({ beat: s }) => {
|
|
|
76
81
|
const [H, w] = a?.legends ?? [], ut = u === "local" ? (t) => C(t).custom ?? (() => !0) : void 0, { transactions: gt, projectedTransaction: A } = wt(g), pt = u === "local" ? gt : void 0, k = n.general.projected_amount, bt = A && k && C(o).custom?.(A) ? {
|
|
77
82
|
heading: k,
|
|
78
83
|
transaction: A
|
|
79
|
-
} : void 0, N = (t,
|
|
80
|
-
E(P.DRAWER_CHART_BAR_CLICKED), _(t),
|
|
81
|
-
},
|
|
84
|
+
} : void 0, N = (t, f) => {
|
|
85
|
+
E(P.DRAWER_CHART_BAR_CLICKED), _(t), f !== void 0 && O(f);
|
|
86
|
+
}, ft = (t) => {
|
|
82
87
|
E(P.TAB_SELECTED), _(t);
|
|
83
88
|
}, K = d ? Et([{
|
|
84
89
|
...H,
|
|
@@ -86,9 +91,9 @@ var pe = Pt(({ beat: s }) => {
|
|
|
86
91
|
}, {
|
|
87
92
|
...w,
|
|
88
93
|
value: i[o]?.comparison
|
|
89
|
-
}]) : void 0,
|
|
94
|
+
}]) : void 0, ht = Y?.(n) ?? [], M = d ? (K ?? []).map((t, f) => ({
|
|
90
95
|
label: t.label,
|
|
91
|
-
ariaLabel:
|
|
96
|
+
ariaLabel: ht[f]
|
|
92
97
|
})) : nt ? p.flatMap((t) => t.localized_label ? [{ label: t.localized_label }] : []) : [], _t = d || e === "singleBar" || e === "upcomingBill" ? l.length ? d ? /* @__PURE__ */ r(xt, {
|
|
93
98
|
bars: i,
|
|
94
99
|
colors: G,
|
|
@@ -122,7 +127,7 @@ var pe = Pt(({ beat: s }) => {
|
|
|
122
127
|
projectedRow: bt,
|
|
123
128
|
heading: y?.heading,
|
|
124
129
|
icon: y?.icon,
|
|
125
|
-
onSelectionChange:
|
|
130
|
+
onSelectionChange: ft,
|
|
126
131
|
selectedIndex: o,
|
|
127
132
|
subHeadingText: y?.subHeadingText,
|
|
128
133
|
tabs: l,
|
package/dist/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransactionHistoryBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.tsx"],"sourcesContent":["import { useLayoutEffect, useState } from 'react'\n\nimport { useTheme } from '@mui/material/styles'\nimport { observer } from 'mobx-react-lite'\n\nimport type { Beat, DataSeries, Transaction } from '../../../core'\nimport { formatCurrency } from '../../../core/utils/NumberFormatting'\nimport { ANALYTICS_EVENTS_CONFIG, useAnalyticsEvent } from '../../analytics'\nimport { BarChartV2 } from '../../components/chartblocks/BarchartV2'\nimport { DoubleBarChart } from '../../components/chartblocks/DoubleBarChart'\nimport { EmptyBarChart } from '../../components/chartblocks/EmptyBarChart'\nimport { BAR_ARIA_LABEL } from '../../blocks/ChartBlocks/BarchartBlock/utils'\nimport {\n COMPARISON_BAR_ARIA_LABEL,\n toLegendItems,\n} from '../../blocks/ChartBlocks/DoubleBarChartBlock/comparisonUtils'\n\nimport { TransactionDrillDown } from './TransactionDrillDown'\nimport { useTransactionHistory } from './useTransactionHistory'\nimport {\n getDefaultSelection,\n getDrillDownConfig,\n getFlattenedDataSeries,\n resolveDrillDownSubHeader,\n splitProjectedTransaction,\n toStoreFilter,\n} from './utils'\nimport {\n Drawer,\n useInsightsV2Store,\n useGlobalCopyStore,\n useGlobalUiStore,\n Loader,\n} from '../../../common'\nimport { TransactionDetailsBlock } from '../TransactionDetailsBlock'\nimport { getDynamicCtaBasedOnTriggers } from '../util'\n\ninterface TransactionHistoryProp {\n beat: Beat\n}\n\nexport const TransactionHistoryBlock = observer(({ beat }: TransactionHistoryProp) => {\n const theme = useTheme()\n\n const { actionDrawer, setActionContext } = useInsightsV2Store()\n\n const { insights_payload } = beat\n\n const dynamicCtaVariant = getDynamicCtaBasedOnTriggers(\n actionDrawer,\n insights_payload?.primary_cta,\n insights_payload?.secondary_cta,\n insights_payload?.in_description_cta,\n )?.variant\n\n const {\n top,\n filterMode,\n header,\n colors,\n filterStrategy,\n tipSection,\n subTabAriaLabels,\n slotData,\n beatTransactions: resolveBeatTransactions,\n hideListHeader,\n } = getDrillDownConfig(dynamicCtaVariant ?? '')\n\n const {\n instanceSlotData,\n beatTransactions,\n spendCategoryGuids,\n allAccountGuids,\n categoryName,\n displayedDateRange,\n transactionStore,\n } = useTransactionHistory(beat, { slotData, beatTransactions: resolveBeatTransactions })\n\n const { common: commonCopy, insights_feed: insightsCopy } = useGlobalCopyStore()\n\n const { isCopyLoaded } = useGlobalUiStore()\n\n const [isTransactionDetailsOpen, setIsTransactionDetailsOpen] = useState(false)\n\n const [closeTipSection, setCloseTipSection] = useState(false)\n\n const fireAnalyticsEvent = useAnalyticsEvent()\n\n // `getFlattenedDataSeries` runs ahead of the copy-loaded guard below, hence the literal fallback.\n const { dataSeries, chartData, comparisonBars } = getFlattenedDataSeries({\n data: instanceSlotData?.data_series as DataSeries,\n ariaLabel:\n top === 'doubleBar'\n ? (insightsCopy?.general?.in_month_you_had_a_category_of_amount ??\n COMPARISON_BAR_ARIA_LABEL)\n : BAR_ARIA_LABEL,\n top,\n beatTransactions,\n })\n\n const isDoubleBar = top === 'doubleBar'\n const isTabsOnly = top === 'tabsOnly'\n // The drawer's columns (and tabs): the double bar's month pairs, or the single bar's bars.\n const bars = isDoubleBar ? comparisonBars : chartData\n\n const { chartBarIndex, subTabIndex } = getDefaultSelection({\n chartTabCount: chartData.length,\n comparisonTabCount: comparisonBars.length,\n actionDrawer,\n beatGuid: beat.guid,\n })\n\n const [selectedIndex, setSelectedIndex] = useState(chartBarIndex)\n // The double-bar drawer's series tab (0 = main, e.g. Spend; 1 = comparison, e.g. Income).\n const [subIndex, setSubIndex] = useState(subTabIndex)\n\n // Bars built from the transaction store (Upcoming Bill's paid bills) can arrive after the drawer\n // opens: re-select the default when the bar count changes. Payload-built bars never change count.\n const [defaultedTabCount, setDefaultedTabCount] = useState(bars.length)\n if (bars.length !== defaultedTabCount) {\n setDefaultedTabCount(bars.length)\n setSelectedIndex(chartBarIndex)\n }\n\n const resolveFilter = (index: number) =>\n filterStrategy({\n beat,\n beatTransactions,\n dataSeries,\n comparisonBars,\n selectedIndex: index,\n subIndex,\n isLastTab: index === bars.length - 1,\n instanceSlotData,\n spendCategoryGuids,\n displayedDateRange,\n })\n\n // Apply the current selection's filter. Store variants route through the store filter; local\n // (upcomingBill) variants filter beatTransactions directly and only backfill the cache when the\n // Stable primitive keys for the collections below: beatTransactions/spendCategoryGuids/\n // allAccountGuids are recomputed (new array identity) every render, so depending on them directly\n // re-fires the effect each render (REVIEW.md MEW-3732). Key on content instead.\n const beatTransactionGuidsKey = beatTransactions.map((transaction) => transaction.guid).join(',')\n const spendCategoryGuidsKey = spendCategoryGuids.join(',')\n const allAccountGuidsKey = allAccountGuids.join(',')\n\n // Re-applies the current selection's filter to the shared transactionStore. Called both by the\n // effect below (on selection change) and when the nested TransactionDetailsBlock drawer closes:\n // AddMerchantBudgetAction (opened from within that drawer) overwrites transactionStore.filter\n // with a merchant_guid-only filter while its own drawer is open, and never restores it — leaving\n // the row list stuck showing only that one merchant's transactions after backing all the way out.\n // Re-running this on close re-derives the filter fresh instead of relying on whatever the nested\n // drawers left behind.\n // A `list`, `tabsOnly` or `monthTabs` drawer applies its filter even with no bars or rows.\n const canApplyFilter =\n top === 'list' || top === 'tabsOnly' || top === 'monthTabs' || dataSeries.length > 0\n const applyCurrentSelectionFilter = () => {\n if (filterMode !== 'store' || !canApplyFilter) return\n transactionStore.setFilter(\n toStoreFilter(resolveFilter(selectedIndex), { allAccountGuids, displayedDateRange }),\n )\n }\n\n // store variants route the selection through the shared store filter; local (upcomingBill) variants\n // filter beatTransactions directly, so there's nothing to push to the store for them.\n useLayoutEffect(() => {\n applyCurrentSelectionFilter()\n }, [\n beat.guid,\n selectedIndex,\n subIndex,\n filterMode,\n dataSeries.length,\n instanceSlotData,\n displayedDateRange,\n spendCategoryGuidsKey,\n allAccountGuidsKey,\n beatTransactionGuidsKey,\n top,\n ])\n\n const subHeader = resolveDrillDownSubHeader(insights_payload)\n\n const headerContent = header?.({\n title: insights_payload?.title,\n subHeader,\n categoryName,\n selectedDate: chartData[selectedIndex]?.date ?? undefined,\n })\n\n const barColors = colors(theme, instanceSlotData?.category_guid)\n\n if (!isCopyLoaded) return <Loader />\n\n // `legends` is the block's additional_data ([0] main, [1] second series), as BarchartBlock reads it.\n const [mainLegend, comparisonLegend] = instanceSlotData?.legends ?? []\n\n const transactionFilter =\n filterMode === 'local'\n ? (index: number) => resolveFilter(index).custom ?? (() => true)\n : undefined\n\n // A variant's `beatTransactions` resolver may append a synthesized projected bill (Upcoming Bill's)\n // last: it draws the last bar but is never a list row or a CSV line. Its tab (the one whose filter\n // matches it) shows it as its own row under the copy service's \"Projected amount\"; no row without\n // that copy.\n const { transactions: listTransactions, projectedTransaction } =\n splitProjectedTransaction(beatTransactions)\n\n const baseTransactions: Transaction[] | undefined =\n filterMode === 'local' ? listTransactions : undefined\n\n const projectedHeading = insightsCopy.general.projected_amount\n const projectedRow =\n projectedTransaction &&\n projectedHeading &&\n resolveFilter(selectedIndex).custom?.(projectedTransaction)\n ? { heading: projectedHeading, transaction: projectedTransaction }\n : undefined\n\n // A chart bar click selects its column (and, on the double bar, its series); a month tab change\n // selects the month. MUI Tabs only fires onChange on real user selection, not on mount. The\n // series (Spend / Income) tab fires its own analytics via the shared SubTabs component.\n const selectBar = (index: number, seriesIndex?: number) => {\n fireAnalyticsEvent(ANALYTICS_EVENTS_CONFIG.DRAWER_CHART_BAR_CLICKED)\n setSelectedIndex(index)\n if (seriesIndex !== undefined) setSubIndex(seriesIndex)\n }\n const selectMonth = (index: number) => {\n fireAnalyticsEvent(ANALYTICS_EVENTS_CONFIG.TAB_SELECTED)\n setSelectedIndex(index)\n }\n\n // Double bar: the Spend / Income labels come from copy (additional_data); the legend carries the\n // selected month's two totals, and its labels name the series tabs.\n const doubleBarLegends = isDoubleBar\n ? toLegendItems([\n { ...mainLegend, value: comparisonBars[selectedIndex]?.main },\n { ...comparisonLegend, value: comparisonBars[selectedIndex]?.comparison },\n ])\n : undefined\n\n // Legacy pulse named these tabs \"Spend transactions\" / \"Income transactions\" for screen readers.\n const seriesAriaLabels = subTabAriaLabels?.(insightsCopy) ?? []\n\n // The second tab row: the double bar's two series, or a tabs-only drawer's sides (one per\n // data_series row, labelled by the row's pre-localized label).\n const sideTabs = isDoubleBar\n ? (doubleBarLegends ?? []).map((legend, index) => ({\n label: legend.label,\n ariaLabel: seriesAriaLabels[index],\n }))\n : isTabsOnly\n ? dataSeries.flatMap((row) => (row.localized_label ? [{ label: row.localized_label }] : []))\n : []\n\n const renderChart = () => {\n if (!bars.length) return <EmptyBarChart />\n\n return isDoubleBar ? (\n <DoubleBarChart\n bars={comparisonBars}\n colors={barColors}\n legendPosition=\"top\"\n legends={doubleBarLegends}\n onBarClick={selectBar}\n selectedIndex={selectedIndex}\n />\n ) : (\n <BarChartV2\n amounts={{\n average: instanceSlotData?.average_amount,\n formattedAverage: formatCurrency(instanceSlotData?.average_amount ?? 0, '0,0.00'),\n }}\n color={barColors[0] ?? theme.palette.primary.main}\n data={chartData}\n legendPosition=\"top\"\n // Figma's chart drawer: 172px of bars under the 24px legend, 200px in all.\n heightOverride={172}\n margin={{\n bottom: 8,\n top: 24,\n left: 24,\n right: 24,\n }}\n // TODO: Temporary fallback while the backend doesn't always send `legends` on every\n // beat/variant. Remove this once the backend guarantees `legends` on the payload.\n monthlyAmountLabel={mainLegend?.label ?? insightsCopy.general.monthly_amount}\n onBarClick={selectBar}\n selectedTabIndex={selectedIndex}\n trendAmountLabel={comparisonLegend?.label ?? insightsCopy.general.trend}\n />\n )\n }\n\n // `list` (Low Account Balance), `tabsOnly` (Non-Essential Category Summary) and `monthTabs`\n // (Monthly Subscription Aggregate) drawers have no chart above their transaction list.\n const chartTop =\n isDoubleBar || top === 'singleBar' || top === 'upcomingBill' ? renderChart() : null\n\n return (\n <>\n <TransactionDrillDown\n baseTransactions={baseTransactions}\n hideListHeader={hideListHeader}\n projectedRow={projectedRow}\n heading={headerContent?.heading}\n icon={headerContent?.icon}\n onSelectionChange={selectMonth}\n selectedIndex={selectedIndex}\n subHeadingText={headerContent?.subHeadingText}\n tabs={bars}\n top={chartTop}\n {...(sideTabs.length > 0 && {\n subTabs: { tabs: sideTabs, selectedIndex: subIndex, onChange: setSubIndex },\n })}\n {...(tipSection && {\n tipSection:\n !closeTipSection &&\n (tipSection(setCloseTipSection, insightsCopy, subHeader?.text, instanceSlotData) ??\n false),\n })}\n transactionFilter={transactionFilter}\n onEachRowClick={(guid) => {\n setActionContext({ transaction_guid: guid })\n setIsTransactionDetailsOpen(true)\n }}\n />\n <Drawer\n ariaLabelClose={commonCopy.close_aria}\n isOpen={isTransactionDetailsOpen}\n onClose={() => {\n setIsTransactionDetailsOpen(false)\n // Clear only the key this nested drawer owns — setActionContext merges, so an\n // explicit `{}` would be a no-op and leave transaction_guid stuck in the shared\n // context, and the outer drawer's own context (e.g. chartBarIndex) must survive.\n setActionContext({ transaction_guid: undefined })\n // Re-apply this block's own filter: a further-nested AddMerchantBudgetAction drawer may\n // have overwritten transactionStore.filter (merchant_guid-only) without restoring it.\n applyCurrentSelectionFilter()\n }}\n title={insightsCopy.general.transaction_details_title}\n >\n <TransactionDetailsBlock beat={beat} />\n </Drawer>\n </>\n )\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TransactionHistoryBlock.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionHistoryBlock/TransactionHistoryBlock.tsx"],"sourcesContent":["import { useLayoutEffect, useState } from 'react'\n\nimport { useTheme } from '@mui/material/styles'\nimport { observer } from 'mobx-react-lite'\n\nimport type { Beat, DataSeries, Transaction } from '../../../core'\nimport { formatCurrency } from '../../../core/utils/NumberFormatting'\nimport { ANALYTICS_EVENTS_CONFIG, useAnalyticsEvent } from '../../analytics'\nimport { BarChartV2 } from '../../components/chartblocks/BarchartV2'\nimport { DoubleBarChart } from '../../components/chartblocks/DoubleBarChart'\nimport { EmptyBarChart } from '../../components/chartblocks/EmptyBarChart'\nimport { BAR_ARIA_LABEL } from '../../blocks/ChartBlocks/BarchartBlock/utils'\nimport {\n COMPARISON_BAR_ARIA_LABEL,\n toLegendItems,\n} from '../../blocks/ChartBlocks/DoubleBarChartBlock/comparisonUtils'\n\nimport { TransactionDrillDown } from './TransactionDrillDown'\nimport { useTransactionHistory } from './useTransactionHistory'\nimport {\n getDefaultSelection,\n getDrillDownConfig,\n getFlattenedDataSeries,\n resolveDrillDownSubHeader,\n splitProjectedTransaction,\n toStoreFilter,\n} from './utils'\nimport {\n Drawer,\n useInsightsV2Store,\n useGlobalCopyStore,\n useGlobalUiStore,\n Loader,\n} from '../../../common'\nimport { TransactionDetailsBlock } from '../TransactionDetailsBlock'\nimport { getDynamicCtaBasedOnTriggers } from '../util'\n\ninterface TransactionHistoryProp {\n beat: Beat\n}\n\nexport const TransactionHistoryBlock = observer(({ beat }: TransactionHistoryProp) => {\n const theme = useTheme()\n\n const { actionDrawer, setActionContext } = useInsightsV2Store()\n\n const { insights_payload } = beat\n\n const dynamicCtaVariant = getDynamicCtaBasedOnTriggers(\n actionDrawer,\n insights_payload?.primary_cta,\n insights_payload?.secondary_cta,\n insights_payload?.in_description_cta,\n )?.variant\n\n const {\n top,\n filterMode,\n header,\n colors,\n filterStrategy,\n tipSection,\n subTabAriaLabels,\n slotData,\n beatTransactions: resolveBeatTransactions,\n hideListHeader,\n } = getDrillDownConfig(dynamicCtaVariant ?? '')\n\n const {\n instanceSlotData,\n beatTransactions,\n spendCategoryGuids,\n allAccountGuids,\n categoryName,\n displayedDateRange,\n transactionStore,\n } = useTransactionHistory(beat, { slotData, beatTransactions: resolveBeatTransactions })\n\n const { common: commonCopy, insights_feed: insightsCopy } = useGlobalCopyStore()\n\n const { isCopyLoaded } = useGlobalUiStore()\n\n const [isTransactionDetailsOpen, setIsTransactionDetailsOpen] = useState(false)\n\n const [closeTipSection, setCloseTipSection] = useState(false)\n\n const fireAnalyticsEvent = useAnalyticsEvent()\n\n // `getFlattenedDataSeries` runs ahead of the copy-loaded guard below, hence the literal fallback.\n const { dataSeries, chartData, comparisonBars } = getFlattenedDataSeries({\n data: instanceSlotData?.data_series as DataSeries,\n ariaLabel:\n top === 'doubleBar'\n ? (insightsCopy?.general?.in_month_you_had_a_category_of_amount ??\n COMPARISON_BAR_ARIA_LABEL)\n : BAR_ARIA_LABEL,\n top,\n beatTransactions,\n })\n\n const isDoubleBar = top === 'doubleBar'\n const isTabsOnly = top === 'tabsOnly'\n // The drawer's columns (and tabs): the double bar's month pairs, or the single bar's bars.\n const bars = isDoubleBar ? comparisonBars : chartData\n\n const { chartBarIndex, subTabIndex } = getDefaultSelection({\n chartTabCount: chartData.length,\n comparisonTabCount: comparisonBars.length,\n actionDrawer,\n beatGuid: beat.guid,\n })\n\n const [selectedIndex, setSelectedIndex] = useState(chartBarIndex)\n // The double-bar drawer's series tab (0 = main, e.g. Spend; 1 = comparison, e.g. Income).\n const [subIndex, setSubIndex] = useState(subTabIndex)\n\n // Bars built from the transaction store (Upcoming Bill's paid bills) can arrive after the drawer\n // opens: re-select the default when the bar count changes. Payload-built bars never change count.\n const [defaultedTabCount, setDefaultedTabCount] = useState(bars.length)\n if (bars.length !== defaultedTabCount) {\n setDefaultedTabCount(bars.length)\n setSelectedIndex(chartBarIndex)\n }\n\n const resolveFilter = (index: number) =>\n filterStrategy({\n beat,\n beatTransactions,\n dataSeries,\n comparisonBars,\n selectedIndex: index,\n subIndex,\n isLastTab: index === bars.length - 1,\n instanceSlotData,\n spendCategoryGuids,\n displayedDateRange,\n })\n\n // Apply the current selection's filter. Store variants route through the store filter; local\n // (upcomingBill) variants filter beatTransactions directly and only backfill the cache when the\n // Stable primitive keys for the collections below: beatTransactions/spendCategoryGuids/\n // allAccountGuids are recomputed (new array identity) every render, so depending on them directly\n // re-fires the effect each render (REVIEW.md MEW-3732). Key on content instead.\n const beatTransactionGuidsKey = beatTransactions.map((transaction) => transaction.guid).join(',')\n const spendCategoryGuidsKey = spendCategoryGuids.join(',')\n const allAccountGuidsKey = allAccountGuids.join(',')\n\n // Re-applies the current selection's filter to the shared transactionStore. Called both by the\n // effect below (on selection change) and when the nested TransactionDetailsBlock drawer closes:\n // AddMerchantBudgetAction (opened from within that drawer) overwrites transactionStore.filter\n // with a merchant_guid-only filter while its own drawer is open, and never restores it — leaving\n // the row list stuck showing only that one merchant's transactions after backing all the way out.\n // Re-running this on close re-derives the filter fresh instead of relying on whatever the nested\n // drawers left behind.\n // A `list`, `tabsOnly` or `monthTabs` drawer applies its filter even with no bars or rows.\n const canApplyFilter =\n top === 'list' || top === 'tabsOnly' || top === 'monthTabs' || dataSeries.length > 0\n const applyCurrentSelectionFilter = () => {\n if (filterMode !== 'store') return\n // A chart drawer with no bars can name no rows: fail closed rather than keep the store's default\n // (every account) or a previous drawer's filter.\n const filter = canApplyFilter\n ? resolveFilter(selectedIndex)\n : { accounts: [], custom: () => false }\n transactionStore.setFilter(toStoreFilter(filter, { allAccountGuids, displayedDateRange }))\n }\n\n // store variants route the selection through the shared store filter; local (upcomingBill) variants\n // filter beatTransactions directly, so there's nothing to push to the store for them.\n useLayoutEffect(() => {\n applyCurrentSelectionFilter()\n }, [\n beat.guid,\n selectedIndex,\n subIndex,\n filterMode,\n dataSeries.length,\n instanceSlotData,\n displayedDateRange,\n spendCategoryGuidsKey,\n allAccountGuidsKey,\n beatTransactionGuidsKey,\n top,\n ])\n\n const subHeader = resolveDrillDownSubHeader(insights_payload)\n\n const headerContent = header?.({\n title: insights_payload?.title,\n subHeader,\n categoryName,\n selectedDate: chartData[selectedIndex]?.date ?? undefined,\n })\n\n const barColors = colors(theme, instanceSlotData?.category_guid)\n\n if (!isCopyLoaded) return <Loader />\n\n // `legends` is the block's additional_data ([0] main, [1] second series), as BarchartBlock reads it.\n const [mainLegend, comparisonLegend] = instanceSlotData?.legends ?? []\n\n const transactionFilter =\n filterMode === 'local'\n ? (index: number) => resolveFilter(index).custom ?? (() => true)\n : undefined\n\n // A variant's `beatTransactions` resolver may append a synthesized projected bill (Upcoming Bill's)\n // last: it draws the last bar but is never a list row or a CSV line. Its tab (the one whose filter\n // matches it) shows it as its own row under the copy service's \"Projected amount\"; no row without\n // that copy.\n const { transactions: listTransactions, projectedTransaction } =\n splitProjectedTransaction(beatTransactions)\n\n const baseTransactions: Transaction[] | undefined =\n filterMode === 'local' ? listTransactions : undefined\n\n const projectedHeading = insightsCopy.general.projected_amount\n const projectedRow =\n projectedTransaction &&\n projectedHeading &&\n resolveFilter(selectedIndex).custom?.(projectedTransaction)\n ? { heading: projectedHeading, transaction: projectedTransaction }\n : undefined\n\n // A chart bar click selects its column (and, on the double bar, its series); a month tab change\n // selects the month. MUI Tabs only fires onChange on real user selection, not on mount. The\n // series (Spend / Income) tab fires its own analytics via the shared SubTabs component.\n const selectBar = (index: number, seriesIndex?: number) => {\n fireAnalyticsEvent(ANALYTICS_EVENTS_CONFIG.DRAWER_CHART_BAR_CLICKED)\n setSelectedIndex(index)\n if (seriesIndex !== undefined) setSubIndex(seriesIndex)\n }\n const selectMonth = (index: number) => {\n fireAnalyticsEvent(ANALYTICS_EVENTS_CONFIG.TAB_SELECTED)\n setSelectedIndex(index)\n }\n\n // Double bar: the Spend / Income labels come from copy (additional_data); the legend carries the\n // selected month's two totals, and its labels name the series tabs.\n const doubleBarLegends = isDoubleBar\n ? toLegendItems([\n { ...mainLegend, value: comparisonBars[selectedIndex]?.main },\n { ...comparisonLegend, value: comparisonBars[selectedIndex]?.comparison },\n ])\n : undefined\n\n // Legacy pulse named these tabs \"Spend transactions\" / \"Income transactions\" for screen readers.\n const seriesAriaLabels = subTabAriaLabels?.(insightsCopy) ?? []\n\n // The second tab row: the double bar's two series, or a tabs-only drawer's sides (one per\n // data_series row, labelled by the row's pre-localized label).\n const sideTabs = isDoubleBar\n ? (doubleBarLegends ?? []).map((legend, index) => ({\n label: legend.label,\n ariaLabel: seriesAriaLabels[index],\n }))\n : isTabsOnly\n ? dataSeries.flatMap((row) => (row.localized_label ? [{ label: row.localized_label }] : []))\n : []\n\n const renderChart = () => {\n if (!bars.length) return <EmptyBarChart />\n\n return isDoubleBar ? (\n <DoubleBarChart\n bars={comparisonBars}\n colors={barColors}\n legendPosition=\"top\"\n legends={doubleBarLegends}\n onBarClick={selectBar}\n selectedIndex={selectedIndex}\n />\n ) : (\n <BarChartV2\n amounts={{\n average: instanceSlotData?.average_amount,\n formattedAverage: formatCurrency(instanceSlotData?.average_amount ?? 0, '0,0.00'),\n }}\n color={barColors[0] ?? theme.palette.primary.main}\n data={chartData}\n legendPosition=\"top\"\n // Figma's chart drawer: 172px of bars under the 24px legend, 200px in all.\n heightOverride={172}\n margin={{\n bottom: 8,\n top: 24,\n left: 24,\n right: 24,\n }}\n // TODO: Temporary fallback while the backend doesn't always send `legends` on every\n // beat/variant. Remove this once the backend guarantees `legends` on the payload.\n monthlyAmountLabel={mainLegend?.label ?? insightsCopy.general.monthly_amount}\n onBarClick={selectBar}\n selectedTabIndex={selectedIndex}\n trendAmountLabel={comparisonLegend?.label ?? insightsCopy.general.trend}\n />\n )\n }\n\n // `list` (Low Account Balance), `tabsOnly` (Non-Essential Category Summary) and `monthTabs`\n // (Monthly Subscription Aggregate) drawers have no chart above their transaction list.\n const chartTop =\n isDoubleBar || top === 'singleBar' || top === 'upcomingBill' ? renderChart() : null\n\n return (\n <>\n <TransactionDrillDown\n baseTransactions={baseTransactions}\n hideListHeader={hideListHeader}\n projectedRow={projectedRow}\n heading={headerContent?.heading}\n icon={headerContent?.icon}\n onSelectionChange={selectMonth}\n selectedIndex={selectedIndex}\n subHeadingText={headerContent?.subHeadingText}\n tabs={bars}\n top={chartTop}\n {...(sideTabs.length > 0 && {\n subTabs: { tabs: sideTabs, selectedIndex: subIndex, onChange: setSubIndex },\n })}\n {...(tipSection && {\n tipSection:\n !closeTipSection &&\n (tipSection(setCloseTipSection, insightsCopy, subHeader?.text, instanceSlotData) ??\n false),\n })}\n transactionFilter={transactionFilter}\n onEachRowClick={(guid) => {\n setActionContext({ transaction_guid: guid })\n setIsTransactionDetailsOpen(true)\n }}\n />\n <Drawer\n ariaLabelClose={commonCopy.close_aria}\n isOpen={isTransactionDetailsOpen}\n onClose={() => {\n setIsTransactionDetailsOpen(false)\n // Clear only the key this nested drawer owns — setActionContext merges, so an\n // explicit `{}` would be a no-op and leave transaction_guid stuck in the shared\n // context, and the outer drawer's own context (e.g. chartBarIndex) must survive.\n setActionContext({ transaction_guid: undefined })\n // Re-apply this block's own filter: a further-nested AddMerchantBudgetAction drawer may\n // have overwritten transactionStore.filter (merchant_guid-only) without restoring it.\n applyCurrentSelectionFilter()\n }}\n title={insightsCopy.general.transaction_details_title}\n >\n <TransactionDetailsBlock beat={beat} />\n </Drawer>\n </>\n )\n})\n"],"mappings":"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"}
|
|
@@ -76,6 +76,7 @@ export interface DrillDownVariantConfig {
|
|
|
76
76
|
hideListHeader?: boolean;
|
|
77
77
|
}
|
|
78
78
|
export declare const resolveUpcomingBillSlotData: SlotDataResolver;
|
|
79
|
+
export declare const resolveSavingsContributionsSlotData: SlotDataResolver;
|
|
79
80
|
export declare const resolveUpcomingBillTransactions: BeatTransactionsResolver;
|
|
80
81
|
export declare const layout: Partial<Record<DrillDownVariant, DrillDownVariantConfig>>;
|
|
81
82
|
export declare const FALLBACK: DrillDownVariantConfig;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { buildDynamicCopy as
|
|
2
|
-
import { DATE_FORMATS_INTL as A, formatDate as
|
|
3
|
-
import { TransactionStatus as
|
|
4
|
-
import { fromUnixUTC as
|
|
5
|
-
import { getCategoryColor as
|
|
6
|
-
import { getLayoutConfig as
|
|
7
|
-
import { buildProjectedBillTransaction as
|
|
8
|
-
import { MerchantLogo as
|
|
9
|
-
import { Icon as
|
|
1
|
+
import { buildDynamicCopy as v } from "../../../core/utils/Localization.es.js";
|
|
2
|
+
import { DATE_FORMATS_INTL as A, formatDate as C } from "../../../core/constants/DateFormats.es.js";
|
|
3
|
+
import { TransactionStatus as D } from "../../../core/types/Transaction.es.js";
|
|
4
|
+
import { fromUnixUTC as O } from "../../../core/utils/DateUtil.es.js";
|
|
5
|
+
import { getCategoryColor as G } from "../../../core/utils/CategoryUtil.es.js";
|
|
6
|
+
import { getLayoutConfig as B } from "../../blocks/ChartBlocks/DoubleBarChartBlock/layout.es.js";
|
|
7
|
+
import { buildProjectedBillTransaction as R, calendarDateOf as P, finiteNumberOf as u, guidListOf as b, nonEmptyStringOf as T } from "./utils.es.js";
|
|
8
|
+
import { MerchantLogo as U } from "@mxenabled/mxui";
|
|
9
|
+
import { Icon as w } from "@mxenabled/mx-icons";
|
|
10
10
|
import { jsx as c } from "react/jsx-runtime";
|
|
11
|
-
import { endOfMonth as
|
|
11
|
+
import { endOfMonth as E, fromUnixTime as H, isValid as I, parseISO as d, startOfMonth as L } from "date-fns";
|
|
12
12
|
var n = {
|
|
13
13
|
TRANSACTION: "not_standard_amount",
|
|
14
14
|
CATEGORY_SPEND: "category_spend",
|
|
@@ -23,125 +23,139 @@ var n = {
|
|
|
23
23
|
DEPOSIT_SUMMARY: "monthly_deposit_summary",
|
|
24
24
|
LOW_BALANCE: "low_balance",
|
|
25
25
|
SUBSCRIPTION_AGGREGATE: "monthly_subscription_aggregate"
|
|
26
|
-
},
|
|
27
|
-
start:
|
|
28
|
-
end:
|
|
29
|
-
}),
|
|
30
|
-
const { account_guid: e, from_date:
|
|
31
|
-
return typeof e != "string" || typeof
|
|
26
|
+
}, l = (t, e) => e && G(e, t) || t.palette.primary.main, m = (t) => typeof t == "string" ? d(t) : new Date(t ?? NaN), g = (t) => ({
|
|
27
|
+
start: L(m(t)),
|
|
28
|
+
end: E(m(t))
|
|
29
|
+
}), M = (t) => t?.length ? { accounts: t } : {}, Y = ({ beat: t }) => {
|
|
30
|
+
const { account_guid: e, from_date: a, to_date: o } = t.insights_payload?.drilldown_data ?? {};
|
|
31
|
+
return typeof e != "string" || typeof a != "number" || typeof o != "number" || !Number.isFinite(a) || !Number.isFinite(o) || o <= a ? {
|
|
32
32
|
accounts: [],
|
|
33
33
|
custom: () => !1
|
|
34
34
|
} : {
|
|
35
35
|
accounts: [e],
|
|
36
|
-
custom: (r) => !r.is_hidden && r.feed_status !==
|
|
36
|
+
custom: (r) => !r.is_hidden && r.feed_status !== D.PENDING,
|
|
37
37
|
showSplits: !0,
|
|
38
38
|
dateRange: {
|
|
39
|
-
start:
|
|
40
|
-
end:
|
|
39
|
+
start: O(a),
|
|
40
|
+
end: O(o - 1)
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
|
-
},
|
|
43
|
+
}, F = (t) => {
|
|
44
44
|
const e = t?.drilldown_data ?? {};
|
|
45
45
|
return {
|
|
46
|
-
transaction_guids:
|
|
47
|
-
date:
|
|
48
|
-
amount:
|
|
46
|
+
transaction_guids: b(e.transaction_guids),
|
|
47
|
+
date: P(e.due_date),
|
|
48
|
+
amount: u(e.amount),
|
|
49
49
|
category_guid: T(e.category_guid),
|
|
50
|
-
average_amount:
|
|
50
|
+
average_amount: u(e.average_amount),
|
|
51
51
|
merchant_name: T(e.merchant_name)
|
|
52
52
|
};
|
|
53
|
-
},
|
|
54
|
-
const
|
|
55
|
-
return
|
|
56
|
-
},
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
}, V = (t) => {
|
|
54
|
+
const e = t;
|
|
55
|
+
return Array.isArray(e?.names) && Array.isArray(e?.data) ? e : void 0;
|
|
56
|
+
}, W = (t) => {
|
|
57
|
+
const e = t?.drilldown_data ?? {};
|
|
58
|
+
return {
|
|
59
|
+
account_guids: b(e.account_guids),
|
|
60
|
+
data_series: V(e.data_series),
|
|
61
|
+
average_amount: u(e.average_amount)
|
|
62
|
+
};
|
|
63
|
+
}, x = (t, e) => {
|
|
64
|
+
const a = R(t, e);
|
|
65
|
+
return a ? [...t, a] : t;
|
|
66
|
+
}, j = (t, e, a, o) => {
|
|
67
|
+
const r = o?.merchant_name, s = o?.date;
|
|
68
|
+
if (!r || !s) return;
|
|
69
|
+
const i = e.transaction_history_block?.upcoming_bill_tip_message;
|
|
70
|
+
if (i)
|
|
61
71
|
return {
|
|
62
|
-
message:
|
|
72
|
+
message: v(i, /* @__PURE__ */ c("strong", { children: e.general.tip_section_title }), r, C(d(s), A.FULL_MONTH_DAY)),
|
|
63
73
|
onDismiss: () => t(!0)
|
|
64
74
|
};
|
|
65
|
-
},
|
|
66
|
-
const
|
|
75
|
+
}, p = ({ beatTransactions: t, dataSeries: e, selectedIndex: a }) => {
|
|
76
|
+
const o = e[a], r = o?.guid, s = t.find((i) => i.guid === r)?.has_been_split;
|
|
67
77
|
return {
|
|
68
|
-
custom: (
|
|
69
|
-
dateRange:
|
|
70
|
-
showSplits: !
|
|
78
|
+
custom: (i) => i.guid === r,
|
|
79
|
+
dateRange: g(o?.date),
|
|
80
|
+
showSplits: !s
|
|
71
81
|
};
|
|
72
|
-
},
|
|
73
|
-
const
|
|
82
|
+
}, z = ({ dataSeries: t, selectedIndex: e, instanceSlotData: a }) => {
|
|
83
|
+
const o = t[e], r = a.category_guid ?? o?.guid;
|
|
74
84
|
return {
|
|
75
|
-
custom: (
|
|
76
|
-
dateRange:
|
|
85
|
+
custom: (s) => s.category_guid === r || s.top_level_category_guid === r,
|
|
86
|
+
dateRange: g(o?.date)
|
|
77
87
|
};
|
|
78
|
-
},
|
|
88
|
+
}, k = ({ dataSeries: t, selectedIndex: e, spendCategoryGuids: a, instanceSlotData: o }) => {
|
|
79
89
|
const r = t[e];
|
|
80
90
|
return {
|
|
81
|
-
custom: (
|
|
82
|
-
dateRange:
|
|
83
|
-
...
|
|
91
|
+
custom: (s) => !!s.top_level_category_guid && a.includes(s.top_level_category_guid),
|
|
92
|
+
dateRange: g(r?.date),
|
|
93
|
+
...M(o.account_guids)
|
|
84
94
|
};
|
|
85
|
-
},
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
}, K = (t) => typeof t == "number" ? H(t) : d(t ?? ""), $ = ({ beat: t, dataSeries: e, selectedIndex: a, isLastTab: o, instanceSlotData: r }) => {
|
|
96
|
+
const s = e[a], { start: i, end: f } = g(s?.date), _ = K(t.created_at), S = r.account_guids, N = {
|
|
97
|
+
start: i,
|
|
98
|
+
end: o && I(_) && _ < f ? _ : f
|
|
99
|
+
};
|
|
100
|
+
return S?.length ? {
|
|
101
|
+
dateRange: N,
|
|
102
|
+
accounts: S
|
|
103
|
+
} : {
|
|
104
|
+
dateRange: N,
|
|
105
|
+
accounts: [],
|
|
106
|
+
custom: () => !1
|
|
93
107
|
};
|
|
94
|
-
}, y = (t, e,
|
|
95
|
-
const
|
|
96
|
-
return !
|
|
108
|
+
}, y = (t, e, a) => {
|
|
109
|
+
const o = new Set(t), r = d(e ?? "");
|
|
110
|
+
return !o.size || !I(r) ? {
|
|
97
111
|
accounts: [],
|
|
98
112
|
custom: () => !1
|
|
99
113
|
} : {
|
|
100
|
-
custom: (
|
|
114
|
+
custom: (s) => o.has(s.guid),
|
|
101
115
|
dateRange: {
|
|
102
|
-
start:
|
|
103
|
-
end:
|
|
116
|
+
start: L(r),
|
|
117
|
+
end: E(r)
|
|
104
118
|
},
|
|
105
|
-
...
|
|
119
|
+
...M(a)
|
|
106
120
|
};
|
|
107
|
-
},
|
|
108
|
-
const r = e[
|
|
109
|
-
return y(
|
|
110
|
-
},
|
|
121
|
+
}, q = ({ beat: t, dataSeries: e, selectedIndex: a }) => y(e[a]?.aggregated_guids, e[a]?.date, t.account_guids), h = ({ beat: t, comparisonBars: e, selectedIndex: a, subIndex: o }) => {
|
|
122
|
+
const r = e[a];
|
|
123
|
+
return y(o === 1 ? r?.comparisonGuids : r?.mainGuids, r?.date, t.account_guids);
|
|
124
|
+
}, J = ({ beat: t, dataSeries: e, subIndex: a }) => y(e[a]?.aggregated_guids, e[a]?.date, t.account_guids), ct = {
|
|
111
125
|
[n.TRANSACTION]: {
|
|
112
126
|
top: "singleBar",
|
|
113
127
|
scope: "store",
|
|
114
128
|
filterMode: "store",
|
|
115
|
-
filterStrategy:
|
|
116
|
-
colors: (t, e) => [
|
|
129
|
+
filterStrategy: p,
|
|
130
|
+
colors: (t, e) => [l(t, e)],
|
|
117
131
|
header: (t) => {
|
|
118
|
-
const { title: e, subHeader:
|
|
132
|
+
const { title: e, subHeader: a } = t;
|
|
119
133
|
return {
|
|
120
134
|
heading: e,
|
|
121
|
-
subHeadingText:
|
|
122
|
-
icon: /* @__PURE__ */ c(
|
|
123
|
-
alt:
|
|
124
|
-
categoryGuid:
|
|
125
|
-
merchantGuid:
|
|
135
|
+
subHeadingText: a?.text || void 0,
|
|
136
|
+
icon: /* @__PURE__ */ c(U, {
|
|
137
|
+
alt: a?.text,
|
|
138
|
+
categoryGuid: a?.logo?.category_guid || "",
|
|
139
|
+
merchantGuid: a?.logo?.merchant_guid || "",
|
|
126
140
|
size: 16
|
|
127
141
|
})
|
|
128
142
|
};
|
|
129
143
|
},
|
|
130
|
-
tipSection: (t, e,
|
|
131
|
-
message:
|
|
144
|
+
tipSection: (t, e, a = "") => ({
|
|
145
|
+
message: v(e.general.tip_section_message, /* @__PURE__ */ c("strong", { children: e.general.tip_section_title }), /* @__PURE__ */ c("strong", { children: a })),
|
|
132
146
|
onDismiss: () => t(!0)
|
|
133
147
|
})
|
|
134
148
|
},
|
|
135
149
|
[n.CATEGORY_SPEND]: {
|
|
136
150
|
top: "singleBar",
|
|
137
|
-
filterStrategy:
|
|
151
|
+
filterStrategy: z,
|
|
138
152
|
scope: "store",
|
|
139
153
|
filterMode: "store",
|
|
140
|
-
colors: (t, e) => [
|
|
154
|
+
colors: (t, e) => [l(t, e)],
|
|
141
155
|
header: (t) => ({
|
|
142
156
|
heading: t.categoryName,
|
|
143
|
-
subHeadingText: t.selectedDate &&
|
|
144
|
-
icon: /* @__PURE__ */ c(
|
|
157
|
+
subHeadingText: t.selectedDate && C(m(t.selectedDate), A.MONTH_LONG) || "",
|
|
158
|
+
icon: /* @__PURE__ */ c(w, {
|
|
145
159
|
name: "calendar_month",
|
|
146
160
|
size: 16
|
|
147
161
|
})
|
|
@@ -149,32 +163,33 @@ var n = {
|
|
|
149
163
|
},
|
|
150
164
|
[n.MONTHLY_SPEND]: {
|
|
151
165
|
top: "singleBar",
|
|
152
|
-
filterStrategy:
|
|
166
|
+
filterStrategy: k,
|
|
153
167
|
scope: "store",
|
|
154
168
|
filterMode: "store",
|
|
155
169
|
colors: (t) => [t.palette.chart.chart2 ?? t.palette.primary.main]
|
|
156
170
|
},
|
|
157
171
|
[n.ACCOUNT_BALANCE]: {
|
|
158
172
|
top: "singleBar",
|
|
159
|
-
filterStrategy:
|
|
173
|
+
filterStrategy: $,
|
|
174
|
+
slotData: W,
|
|
160
175
|
scope: "store",
|
|
161
176
|
filterMode: "store",
|
|
162
177
|
colors: (t) => [t.palette.success.main]
|
|
163
178
|
},
|
|
164
179
|
[n.UPCOMING_BILL]: {
|
|
165
180
|
top: "upcomingBill",
|
|
166
|
-
filterStrategy:
|
|
181
|
+
filterStrategy: p,
|
|
167
182
|
scope: "beat",
|
|
168
183
|
filterMode: "local",
|
|
169
|
-
colors: (t, e) => [
|
|
170
|
-
slotData:
|
|
171
|
-
beatTransactions:
|
|
184
|
+
colors: (t, e) => [l(t, e)],
|
|
185
|
+
slotData: F,
|
|
186
|
+
beatTransactions: x,
|
|
172
187
|
fetchMissingTransactions: (t) => t.insights_payload?.drilldown_data?.transaction_guids,
|
|
173
|
-
tipSection:
|
|
188
|
+
tipSection: j
|
|
174
189
|
},
|
|
175
190
|
[n.LOW_BALANCE]: {
|
|
176
191
|
top: "list",
|
|
177
|
-
filterStrategy:
|
|
192
|
+
filterStrategy: Y,
|
|
178
193
|
scope: "store",
|
|
179
194
|
filterMode: "store",
|
|
180
195
|
colors: (t) => [t.palette.primary.main],
|
|
@@ -182,47 +197,48 @@ var n = {
|
|
|
182
197
|
},
|
|
183
198
|
[n.SUBSCRIPTION_AGGREGATE]: {
|
|
184
199
|
top: "monthTabs",
|
|
185
|
-
filterStrategy:
|
|
200
|
+
filterStrategy: q,
|
|
186
201
|
scope: "store",
|
|
187
202
|
filterMode: "store",
|
|
188
203
|
colors: (t) => [t.palette.primary.main]
|
|
189
204
|
},
|
|
190
205
|
[n.SPEND_TO_INCOME]: {
|
|
191
206
|
top: "doubleBar",
|
|
192
|
-
filterStrategy:
|
|
207
|
+
filterStrategy: h,
|
|
193
208
|
scope: "store",
|
|
194
209
|
filterMode: "store",
|
|
195
|
-
colors: (t) =>
|
|
210
|
+
colors: (t) => B(t, "spend_to_income")?.color ?? [],
|
|
196
211
|
subTabAriaLabels: (t) => [t.general.spend_transactions, t.general.income_transactions]
|
|
197
212
|
},
|
|
198
213
|
[n.COST_OF_LIVING]: {
|
|
199
214
|
top: "doubleBar",
|
|
200
|
-
filterStrategy:
|
|
215
|
+
filterStrategy: h,
|
|
201
216
|
scope: "store",
|
|
202
217
|
filterMode: "store",
|
|
203
|
-
colors: (t, e) => [
|
|
218
|
+
colors: (t, e) => [l(t, e), t.palette.chart.chart4 ?? t.palette.primary.main],
|
|
204
219
|
subTabAriaLabels: (t) => [void 0, t.general.income_transactions]
|
|
205
220
|
},
|
|
206
221
|
[n.NON_ESSENTIAL_CATEGORY]: {
|
|
207
222
|
top: "tabsOnly",
|
|
208
|
-
filterStrategy:
|
|
223
|
+
filterStrategy: J,
|
|
209
224
|
scope: "store",
|
|
210
225
|
filterMode: "store",
|
|
211
226
|
colors: (t) => [t.palette.primary.main]
|
|
212
227
|
}
|
|
213
|
-
},
|
|
228
|
+
}, lt = {
|
|
214
229
|
top: "singleBar",
|
|
215
|
-
filterStrategy:
|
|
230
|
+
filterStrategy: p,
|
|
216
231
|
scope: "store",
|
|
217
232
|
filterMode: "store",
|
|
218
|
-
colors: (t, e) => [
|
|
233
|
+
colors: (t, e) => [l(t, e)]
|
|
219
234
|
};
|
|
220
235
|
export {
|
|
221
236
|
n as DRILLDOWN_VARIANTS,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
237
|
+
lt as FALLBACK,
|
|
238
|
+
ct as layout,
|
|
239
|
+
W as resolveSavingsContributionsSlotData,
|
|
240
|
+
F as resolveUpcomingBillSlotData,
|
|
241
|
+
x as resolveUpcomingBillTransactions
|
|
226
242
|
};
|
|
227
243
|
|
|
228
244
|
//# sourceMappingURL=layout.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionHistoryBlock/layout.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport type { Theme } from '@mui/material/styles'\nimport { endOfMonth, fromUnixTime, isValid, parseISO, startOfMonth } from 'date-fns'\nimport { getCategoryColor } from '../../../core/utils/CategoryUtil'\nimport {\n type Beat,\n type InsightsFeedCopy,\n type Subtitle,\n type Transaction,\n TransactionStatus,\n buildDynamicCopy,\n} from '../../../core'\nimport type { TransactionFilter } from '../../../core/stores/TransactionStore'\nimport type {\n DataSet,\n InsightsInstanceSlotData,\n} from '../../../core/types/InsightsInstanceSlotData'\nimport { MerchantLogo } from '@mxenabled/mxui'\nimport { DATE_FORMATS_INTL, formatDate } from '../../../core/constants'\nimport { fromUnixUTC } from '../../../core/utils/DateUtil'\nimport { Icon } from '@mxenabled/mx-icons'\nimport type { ChartDataSeriesItem } from '../../blocks/ChartBlocks/BarchartBlock/utils'\nimport { getLayoutConfig as getDoubleBarLayoutConfig } from '../../blocks/ChartBlocks/DoubleBarChartBlock/layout'\nimport type { TipSectionProps } from '../../../insights/components/shared/ChartDrawerTemplate/TipSection'\nimport {\n type BeatTransactionsResolver,\n buildProjectedBillTransaction,\n calendarDateOf,\n type DrillDownSlotData,\n finiteNumberOf,\n guidListOf,\n nonEmptyStringOf,\n type SlotDataResolver,\n} from './utils'\n\nexport interface DrillDownHeader {\n heading?: string\n subHeadingText?: string\n icon?: ReactNode\n}\n\nexport interface DrillDownHeaderContext {\n title?: string\n subHeader?: Subtitle\n categoryName: string\n selectedDate?: string | Date\n}\n\nexport const DRILLDOWN_VARIANTS = {\n TRANSACTION: 'not_standard_amount',\n CATEGORY_SPEND: 'category_spend',\n MONTHLY_SPEND: 'monthly_spend_comparison',\n ACCOUNT_BALANCE: 'account_balance',\n UPCOMING_BILL: 'upcoming_bill',\n SPEND_TO_INCOME: 'monthly_spend_to_income',\n SAVINGS_TO_INCOME: 'monthly_savings_to_income',\n DEBT_TO_INCOME: 'monthly_debt_to_income',\n COST_OF_LIVING: 'cost_living_to_income',\n NON_ESSENTIAL_CATEGORY: 'non_essential_category_summary',\n DEPOSIT_SUMMARY: 'monthly_deposit_summary',\n LOW_BALANCE: 'low_balance',\n SUBSCRIPTION_AGGREGATE: 'monthly_subscription_aggregate',\n} as const\n\nexport type DrillDownVariant = (typeof DRILLDOWN_VARIANTS)[keyof typeof DRILLDOWN_VARIANTS]\n\n/**\n * One row of the flattened data_series a filterStrategy indexes into: a single-bar monthly total or a\n * synthesized upcoming-bill bar. Fields are optional because the backend keys each row dynamically\n * from data_series.names and not every variant carries a guid/category. Double-bar comparison rows\n * will extend this in the next MR.\n */\n\n// Resolves the top-chart palette from the theme (and the beat's category, when relevant).\nexport type ColorKind = (theme: Theme, categoryGuid?: string) => string[]\n\n// Resolves the dynamic drawer header (heading/sub-heading/icon JSX) from the selection context.\nexport type HeaderKind = (ctx: DrillDownHeaderContext) => DrillDownHeader\n\n/**\n * Everything a filterStrategy needs to turn the currently-selected bar/sub-tab into a\n * TransactionFilter. Data-only so strategies stay pure and testable.\n */\nexport interface FilterContext {\n beat: Beat\n beatTransactions: Transaction[]\n dataSeries: ChartDataSeriesItem[]\n // The double-bar drawer's month columns (main / comparison per month, with their guids); empty\n // for every other top kind.\n comparisonBars: DataSet[]\n selectedIndex: number\n subIndex: number\n isLastTab: boolean\n instanceSlotData: InsightsInstanceSlotData\n spendCategoryGuids: string[]\n displayedDateRange: { start: Date; end: Date }\n}\n\n// Resolves the current selection into a TransactionFilter. Carried on each config row so behavior\n// lives next to the variant it belongs to (no separate strategy registry to chase).\nexport type FilterStrategy = (ctx: FilterContext) => TransactionFilter\n\nexport interface DrillDownVariantConfig {\n // `list`: no chart or tabs above the transaction list (an account's transactions for a window).\n // `monthTabs`: one tab per data_series month and no chart (a month's aggregated transactions).\n top: 'singleBar' | 'upcomingBill' | 'doubleBar' | 'tabsOnly' | 'monthTabs' | 'list'\n filterStrategy: FilterStrategy\n colors: ColorKind\n header?: HeaderKind\n // Where the drawer reads its facts. Absent: the bar chart's `instance_slot.data`.\n slotData?: SlotDataResolver\n // Builds the drawer's transactions from the beat's own. Absent: the beat's own, as they are.\n beatTransactions?: BeatTransactionsResolver\n // On open, returns the guids of the drawer's transactions to fetch by guid when the store lacks\n // them (older than its 90-day window), as pulse's BeatShell loads a legacy beat's: each drawer\n // reads them from wherever its beat keeps them. Absent: nothing is fetched.\n fetchMissingTransactions?: (beat: Beat) => unknown\n // undefined when the facts the tip needs are missing: the drawer shows no tip.\n tipSection?: (\n setCloseTipSection: (flag: boolean) => void,\n copy: InsightsFeedCopy,\n merchantName?: string,\n slotData?: DrillDownSlotData,\n ) => TipSectionProps | undefined\n scope: 'store' | 'beat'\n filterMode: 'store' | 'local'\n // Double bar: accessible names for the series tabs ([0] main, [1] comparison), as legacy pulse.\n subTabAriaLabels?: (copy: InsightsFeedCopy) => (string | undefined)[]\n // The list has no Transactions heading above it; the CSV export stays.\n hideListHeader?: boolean\n}\nconst categoryColor = (theme: Theme, categoryGuid?: string): string =>\n (categoryGuid && getCategoryColor(categoryGuid, theme)) || theme.palette.primary.main\n\nconst toLocalDate = (date: string | number | Date | undefined) =>\n typeof date === 'string' ? parseISO(date) : new Date(date ?? NaN)\n\nconst monthRange = (date: string | number | Date | undefined) => ({\n start: startOfMonth(toLocalDate(date)),\n end: endOfMonth(toLocalDate(date)),\n})\n\n// Scopes a filter to the beat's accounts when the beat carries account_guids; otherwise leaves\n// accounts unset so the store default (all accounts) applies.\nconst accountScope = (accountGuids?: string[]): Pick<TransactionFilter, 'accounts'> =>\n accountGuids?.length ? { accounts: accountGuids } : {}\n\n// Low Account Balance — `drilldown_data` names one account and a window (epoch seconds at UTC\n// midnight, to_date exclusive); only settled, visible rows, a split purchase as its parts, as the\n// pulse drawer listed them. Fails closed: a drawer that cannot name its rows shows none.\nconst accountWindowStrategy: FilterStrategy = ({ beat }) => {\n const { account_guid, from_date, to_date } = beat.insights_payload?.drilldown_data ?? {}\n if (\n typeof account_guid !== 'string' ||\n typeof from_date !== 'number' ||\n typeof to_date !== 'number' ||\n !Number.isFinite(from_date) ||\n !Number.isFinite(to_date) ||\n to_date <= from_date\n ) {\n return { accounts: [], custom: () => false }\n }\n\n return {\n accounts: [account_guid],\n custom: (trn) => !trn.is_hidden && trn.feed_status !== TransactionStatus.PENDING,\n showSplits: true,\n dateRange: { start: fromUnixUTC(from_date), end: fromUnixUTC(to_date - 1) },\n }\n}\n\n// Upcoming Bill: each `drilldown_data` fact is narrowed (utils.ts) and a missing or mistyped one is\n// dropped. A bad due date, amount or guid list draws no chart and lists no rows.\n\nexport const resolveUpcomingBillSlotData: SlotDataResolver = (payload) => {\n const data = payload?.drilldown_data ?? {}\n\n return {\n transaction_guids: guidListOf(data.transaction_guids),\n date: calendarDateOf(data.due_date),\n amount: finiteNumberOf(data.amount),\n category_guid: nonEmptyStringOf(data.category_guid),\n average_amount: finiteNumberOf(data.average_amount),\n merchant_name: nonEmptyStringOf(data.merchant_name),\n }\n}\n\n// Upcoming Bill: the paid bills plus the projected bill (due date, amount) appended last, so the\n// bars and the projected tab's \"Projected amount\" row read the one list.\nexport const resolveUpcomingBillTransactions: BeatTransactionsResolver = (\n transactions,\n slotData,\n) => {\n const projected = buildProjectedBillTransaction(transactions, slotData)\n\n return projected ? [...transactions, projected] : transactions\n}\n\nconst upcomingBillTipSection: DrillDownVariantConfig['tipSection'] = (\n setCloseTipSection,\n copy,\n _merchantName,\n slotData,\n) => {\n const merchantName = slotData?.merchant_name\n const dueDate = slotData?.date\n if (!merchantName || !dueDate) return undefined\n\n // No hard-coded English: no tip until the copy service carries the message.\n const message = copy.transaction_history_block?.upcoming_bill_tip_message\n if (!message) return undefined\n\n return {\n message: buildDynamicCopy(\n message,\n <strong>{copy.general.tip_section_title}</strong>,\n merchantName,\n formatDate(parseISO(dueDate), DATE_FORMATS_INTL.FULL_MONTH_DAY),\n ) as ReactNode,\n onDismiss: () => setCloseTipSection(true),\n }\n}\n\n// ── Single-bar filter strategies (this MR) ────────────────────────────────────────────────────\n\n// BillAmountNotStandard / UpcomingBill — the series carries a transaction (TRN) guid; match a single\n// transaction by guid within its month.\nconst transactionGuidStrategy: FilterStrategy = ({\n beatTransactions,\n dataSeries,\n selectedIndex,\n}) => {\n const seriesItem = dataSeries[selectedIndex]\n const seriesGuid = seriesItem?.guid\n const hasBeenSplit = beatTransactions.find((trn) => trn.guid === seriesGuid)?.has_been_split\n\n return {\n custom: (trn) => trn.guid === seriesGuid,\n dateRange: monthRange(seriesItem?.date),\n showSplits: !hasBeenSplit,\n }\n}\n\n// CategorySpendingV2 — match transactions in the beat's category (or whose top-level category is that\n// guid) within the month.\nconst categoryStrategy: FilterStrategy = ({ dataSeries, selectedIndex, instanceSlotData }) => {\n const seriesItem = dataSeries[selectedIndex]\n const categoryGuid = instanceSlotData.category_guid ?? seriesItem?.guid\n\n return {\n custom: (trn) =>\n trn.category_guid === categoryGuid || trn.top_level_category_guid === categoryGuid,\n dateRange: monthRange(seriesItem?.date),\n }\n}\n\n// MonthlySpendComparisonV2 — no per-bar guid; show all spending-category transactions\n// (income/transfers/investments excluded via spendCategoryGuids) within the month.\nconst spendCategoriesStrategy: FilterStrategy = ({\n dataSeries,\n selectedIndex,\n spendCategoryGuids,\n instanceSlotData,\n}) => {\n const seriesItem = dataSeries[selectedIndex]\n\n return {\n custom: (trn) =>\n !!trn.top_level_category_guid && spendCategoryGuids.includes(trn.top_level_category_guid),\n dateRange: monthRange(seriesItem?.date),\n ...accountScope(instanceSlotData.account_guids),\n }\n}\n\n// The beat's creation: tupac sends epoch seconds on the envelope and an ISO string on the wrapper.\nconst toCreatedAt = (createdAt: Beat['created_at']) =>\n typeof createdAt === 'number' ? fromUnixTime(createdAt) : parseISO(createdAt ?? '')\n\n// SavingsAccountBalancesSummary — balances, not categorized spend: the beat's account within the\n// month. Bars are snapshots, so the most recent month ends at the beat's creation (the partial month\n// its balance was taken in); an older month, or a beat created after its last month ended, keeps the\n// whole month.\nconst accountDateStrategy: FilterStrategy = ({\n beat,\n dataSeries,\n selectedIndex,\n isLastTab,\n instanceSlotData,\n}) => {\n const seriesItem = dataSeries[selectedIndex]\n const { start, end } = monthRange(seriesItem?.date)\n const createdAt = toCreatedAt(beat.created_at)\n\n return {\n dateRange: {\n start,\n end: isLastTab && isValid(createdAt) && createdAt < end ? createdAt : end,\n },\n ...accountScope(\n beat.account_guids?.length ? beat.account_guids : instanceSlotData.account_guids,\n ),\n }\n}\n\n// A row's stored guids within the row's month, scoped to the beat's accounts. parseISO reads the\n// 'yyyy-MM-dd' date as a local calendar day, so the month never shifts with the viewer's time zone.\n// Fails closed: a missing row, guid list or date matches nothing, never the store's displayed range.\nconst guidsWithinMonth = (\n guids: string[] | undefined,\n date: string | undefined,\n accountGuids?: string[],\n): TransactionFilter => {\n const guidSet = new Set(guids)\n const month = parseISO(date ?? '')\n if (!guidSet.size || !isValid(month)) return { accounts: [], custom: () => false }\n\n return {\n custom: (trn) => guidSet.has(trn.guid),\n dateRange: { start: startOfMonth(month), end: endOfMonth(month) },\n ...accountScope(accountGuids),\n }\n}\n\n// Monthly Subscription Aggregate — one data_series row per month, each naming its own\n// aggregated_guids; the selected month tab lists exactly those.\nconst monthlyAggregatedGuidsStrategy: FilterStrategy = ({ beat, dataSeries, selectedIndex }) =>\n guidsWithinMonth(\n dataSeries[selectedIndex]?.aggregated_guids,\n dataSeries[selectedIndex]?.date,\n beat.account_guids,\n )\n\n// ── Double-bar strategies ──────────────────────────────────────────────────────────────────────\n\n// Monthly Spend to Income Comparison — the selected month's main (Spend, sub-tab 0) or comparison\n// (Income, sub-tab 1) bar guids. The V2 payload carries no account_guids on instance_slot.data.\nconst comparisonGuidsStrategy: FilterStrategy = ({\n beat,\n comparisonBars,\n selectedIndex,\n subIndex,\n}) => {\n const bar = comparisonBars[selectedIndex]\n\n return guidsWithinMonth(\n subIndex === 1 ? bar?.comparisonGuids : bar?.mainGuids,\n bar?.date,\n beat.account_guids,\n )\n}\n\n// ── Tabs-only strategies ───────────────────────────────────────────────────────────────────────\n\n// Monthly Non-Essential Category Summary — the series is the two sides of one month (non-essential /\n// essential), not a time series; the selected sub-tab picks the row.\nconst aggregatedGuidsStrategy: FilterStrategy = ({ beat, dataSeries, subIndex }) =>\n guidsWithinMonth(\n dataSeries[subIndex]?.aggregated_guids,\n dataSeries[subIndex]?.date,\n beat.account_guids,\n )\n\nexport const layout: Partial<Record<DrillDownVariant, DrillDownVariantConfig>> = {\n [DRILLDOWN_VARIANTS.TRANSACTION]: {\n top: 'singleBar',\n scope: 'store',\n filterMode: 'store',\n filterStrategy: transactionGuidStrategy,\n colors: (theme: Theme, categoryGuid?: string) => [categoryColor(theme, categoryGuid)],\n header: (ctx: DrillDownHeaderContext) => {\n const { title, subHeader } = ctx\n return {\n heading: title,\n subHeadingText: subHeader?.text || undefined,\n icon: (\n <MerchantLogo\n alt={subHeader?.text}\n categoryGuid={subHeader?.logo?.category_guid || ''}\n merchantGuid={subHeader?.logo?.merchant_guid || ''}\n size={16}\n />\n ),\n }\n },\n tipSection: (setCloseTipSection, copy, merchantName = '') => {\n return {\n message: buildDynamicCopy(\n copy.general.tip_section_message,\n <strong>{copy.general.tip_section_title}</strong>,\n <strong>{merchantName}</strong>,\n ),\n onDismiss: () => setCloseTipSection(true),\n } as TipSectionProps\n },\n },\n [DRILLDOWN_VARIANTS.CATEGORY_SPEND]: {\n top: 'singleBar',\n filterStrategy: categoryStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme, categoryGuid?: string) => [categoryColor(theme, categoryGuid)],\n header: (ctx: DrillDownHeaderContext) => {\n return {\n heading: ctx.categoryName,\n subHeadingText: ctx.selectedDate\n ? formatDate(toLocalDate(ctx.selectedDate), DATE_FORMATS_INTL.MONTH_LONG) || ''\n : '',\n icon: <Icon name=\"calendar_month\" size={16} />,\n }\n },\n },\n [DRILLDOWN_VARIANTS.MONTHLY_SPEND]: {\n top: 'singleBar',\n filterStrategy: spendCategoriesStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.chart.chart2 ?? theme.palette.primary.main],\n },\n [DRILLDOWN_VARIANTS.ACCOUNT_BALANCE]: {\n top: 'singleBar',\n filterStrategy: accountDateStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.success.main],\n },\n // Upcoming Bill: one bar per paid bill plus the projected bill, all from `drilldown_data`; a bar's\n // tab lists that bill alone (filtered locally, as legacy pulse does).\n [DRILLDOWN_VARIANTS.UPCOMING_BILL]: {\n top: 'upcomingBill',\n filterStrategy: transactionGuidStrategy,\n scope: 'beat',\n filterMode: 'local',\n colors: (theme: Theme, categoryGuid?: string) => [categoryColor(theme, categoryGuid)],\n slotData: resolveUpcomingBillSlotData,\n beatTransactions: resolveUpcomingBillTransactions,\n fetchMissingTransactions: (beat) => beat.insights_payload?.drilldown_data?.transaction_guids,\n tipSection: upcomingBillTipSection,\n },\n\n // Low Account Balance's \"Transactions\" link: the account's transactions for the alert's window.\n [DRILLDOWN_VARIANTS.LOW_BALANCE]: {\n top: 'list',\n filterStrategy: accountWindowStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.primary.main],\n // Figma 438:27640 lists the rows straight under the drawer header.\n hideListHeader: true,\n },\n\n // Monthly Subscription Aggregate's \"View Transactions\": one tab per month, no chart, each month\n // listing its own aggregated_guids.\n [DRILLDOWN_VARIANTS.SUBSCRIPTION_AGGREGATE]: {\n top: 'monthTabs',\n filterStrategy: monthlyAggregatedGuidsStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.primary.main],\n },\n\n // ── Double-bar comparison beats (month tabs + main / comparison sub-tabs) ────────────────────\n // Monthly Spend to Income Comparison: Spend vs Income per month, in the card block's palette.\n [DRILLDOWN_VARIANTS.SPEND_TO_INCOME]: {\n top: 'doubleBar',\n filterStrategy: comparisonGuidsStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => getDoubleBarLayoutConfig(theme, 'spend_to_income')?.color ?? [],\n subTabAriaLabels: (copy) => [copy.general.spend_transactions, copy.general.income_transactions],\n },\n // Cost of Living to Income: one living-cost category (Housing, Groceries, ...) vs Income per month.\n // The cost bar takes the beat category's colour and Income the legacy chart4, as the pulse drawer.\n [DRILLDOWN_VARIANTS.COST_OF_LIVING]: {\n top: 'doubleBar',\n filterStrategy: comparisonGuidsStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme, categoryGuid?: string) => [\n categoryColor(theme, categoryGuid),\n theme.palette.chart.chart4 ?? theme.palette.primary.main,\n ],\n subTabAriaLabels: (copy) => [undefined, copy.general.income_transactions],\n },\n // NEXT MR — the other double-bar beats reuse comparisonGuidsStrategy with their own palettes:\n // [DRILLDOWN_VARIANTS.SAVINGS_TO_INCOME]: comparison('savingsVsIncome'),\n // [DRILLDOWN_VARIANTS.DEBT_TO_INCOME]: comparison('debtVsIncome'),\n\n // ── Tabs-only beats (no chart; one side tab per series row) ─────────────────────────────────\n // Monthly Non-Essential Category Summary: the donut card's \"View more\" lists one month's spend by\n // side, Non-essential / Essential, each side its own aggregated_guids. The tabs are labelled by\n // the rows' pre-localized labels, in wire order; a row without one gets no tab.\n [DRILLDOWN_VARIANTS.NON_ESSENTIAL_CATEGORY]: {\n top: 'tabsOnly',\n filterStrategy: aggregatedGuidsStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.primary.main],\n },\n // NEXT MR — [DRILLDOWN_VARIANTS.DEPOSIT_SUMMARY] (WEC-2097, MR !1756).\n}\n\n// Fallback for a missing/typo'd backend variant: a single-bar, store-scoped drawer filtered by the\n// beat's transaction guid and colored by category, so the drawer still renders instead of crashing.\nexport const FALLBACK: DrillDownVariantConfig = {\n top: 'singleBar',\n filterStrategy: transactionGuidStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme, categoryGuid?: string) => [categoryColor(theme, categoryGuid)],\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"layout.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionHistoryBlock/layout.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport type { Theme } from '@mui/material/styles'\nimport { endOfMonth, fromUnixTime, isValid, parseISO, startOfMonth } from 'date-fns'\nimport { getCategoryColor } from '../../../core/utils/CategoryUtil'\nimport {\n type Beat,\n type DataSeries,\n type InsightsFeedCopy,\n type Subtitle,\n type Transaction,\n TransactionStatus,\n buildDynamicCopy,\n} from '../../../core'\nimport type { TransactionFilter } from '../../../core/stores/TransactionStore'\nimport type {\n DataSet,\n InsightsInstanceSlotData,\n} from '../../../core/types/InsightsInstanceSlotData'\nimport { MerchantLogo } from '@mxenabled/mxui'\nimport { DATE_FORMATS_INTL, formatDate } from '../../../core/constants'\nimport { fromUnixUTC } from '../../../core/utils/DateUtil'\nimport { Icon } from '@mxenabled/mx-icons'\nimport type { ChartDataSeriesItem } from '../../blocks/ChartBlocks/BarchartBlock/utils'\nimport { getLayoutConfig as getDoubleBarLayoutConfig } from '../../blocks/ChartBlocks/DoubleBarChartBlock/layout'\nimport type { TipSectionProps } from '../../../insights/components/shared/ChartDrawerTemplate/TipSection'\nimport {\n type BeatTransactionsResolver,\n buildProjectedBillTransaction,\n calendarDateOf,\n type DrillDownSlotData,\n finiteNumberOf,\n guidListOf,\n nonEmptyStringOf,\n type SlotDataResolver,\n} from './utils'\n\nexport interface DrillDownHeader {\n heading?: string\n subHeadingText?: string\n icon?: ReactNode\n}\n\nexport interface DrillDownHeaderContext {\n title?: string\n subHeader?: Subtitle\n categoryName: string\n selectedDate?: string | Date\n}\n\nexport const DRILLDOWN_VARIANTS = {\n TRANSACTION: 'not_standard_amount',\n CATEGORY_SPEND: 'category_spend',\n MONTHLY_SPEND: 'monthly_spend_comparison',\n ACCOUNT_BALANCE: 'account_balance',\n UPCOMING_BILL: 'upcoming_bill',\n SPEND_TO_INCOME: 'monthly_spend_to_income',\n SAVINGS_TO_INCOME: 'monthly_savings_to_income',\n DEBT_TO_INCOME: 'monthly_debt_to_income',\n COST_OF_LIVING: 'cost_living_to_income',\n NON_ESSENTIAL_CATEGORY: 'non_essential_category_summary',\n DEPOSIT_SUMMARY: 'monthly_deposit_summary',\n LOW_BALANCE: 'low_balance',\n SUBSCRIPTION_AGGREGATE: 'monthly_subscription_aggregate',\n} as const\n\nexport type DrillDownVariant = (typeof DRILLDOWN_VARIANTS)[keyof typeof DRILLDOWN_VARIANTS]\n\n/**\n * One row of the flattened data_series a filterStrategy indexes into: a single-bar monthly total or a\n * synthesized upcoming-bill bar. Fields are optional because the backend keys each row dynamically\n * from data_series.names and not every variant carries a guid/category. Double-bar comparison rows\n * will extend this in the next MR.\n */\n\n// Resolves the top-chart palette from the theme (and the beat's category, when relevant).\nexport type ColorKind = (theme: Theme, categoryGuid?: string) => string[]\n\n// Resolves the dynamic drawer header (heading/sub-heading/icon JSX) from the selection context.\nexport type HeaderKind = (ctx: DrillDownHeaderContext) => DrillDownHeader\n\n/**\n * Everything a filterStrategy needs to turn the currently-selected bar/sub-tab into a\n * TransactionFilter. Data-only so strategies stay pure and testable.\n */\nexport interface FilterContext {\n beat: Beat\n beatTransactions: Transaction[]\n dataSeries: ChartDataSeriesItem[]\n // The double-bar drawer's month columns (main / comparison per month, with their guids); empty\n // for every other top kind.\n comparisonBars: DataSet[]\n selectedIndex: number\n subIndex: number\n isLastTab: boolean\n instanceSlotData: InsightsInstanceSlotData\n spendCategoryGuids: string[]\n displayedDateRange: { start: Date; end: Date }\n}\n\n// Resolves the current selection into a TransactionFilter. Carried on each config row so behavior\n// lives next to the variant it belongs to (no separate strategy registry to chase).\nexport type FilterStrategy = (ctx: FilterContext) => TransactionFilter\n\nexport interface DrillDownVariantConfig {\n // `list`: no chart or tabs above the transaction list (an account's transactions for a window).\n // `monthTabs`: one tab per data_series month and no chart (a month's aggregated transactions).\n top: 'singleBar' | 'upcomingBill' | 'doubleBar' | 'tabsOnly' | 'monthTabs' | 'list'\n filterStrategy: FilterStrategy\n colors: ColorKind\n header?: HeaderKind\n // Where the drawer reads its facts. Absent: the bar chart's `instance_slot.data`.\n slotData?: SlotDataResolver\n // Builds the drawer's transactions from the beat's own. Absent: the beat's own, as they are.\n beatTransactions?: BeatTransactionsResolver\n // On open, returns the guids of the drawer's transactions to fetch by guid when the store lacks\n // them (older than its 90-day window), as pulse's BeatShell loads a legacy beat's: each drawer\n // reads them from wherever its beat keeps them. Absent: nothing is fetched.\n fetchMissingTransactions?: (beat: Beat) => unknown\n // undefined when the facts the tip needs are missing: the drawer shows no tip.\n tipSection?: (\n setCloseTipSection: (flag: boolean) => void,\n copy: InsightsFeedCopy,\n merchantName?: string,\n slotData?: DrillDownSlotData,\n ) => TipSectionProps | undefined\n scope: 'store' | 'beat'\n filterMode: 'store' | 'local'\n // Double bar: accessible names for the series tabs ([0] main, [1] comparison), as legacy pulse.\n subTabAriaLabels?: (copy: InsightsFeedCopy) => (string | undefined)[]\n // The list has no Transactions heading above it; the CSV export stays.\n hideListHeader?: boolean\n}\nconst categoryColor = (theme: Theme, categoryGuid?: string): string =>\n (categoryGuid && getCategoryColor(categoryGuid, theme)) || theme.palette.primary.main\n\nconst toLocalDate = (date: string | number | Date | undefined) =>\n typeof date === 'string' ? parseISO(date) : new Date(date ?? NaN)\n\nconst monthRange = (date: string | number | Date | undefined) => ({\n start: startOfMonth(toLocalDate(date)),\n end: endOfMonth(toLocalDate(date)),\n})\n\n// Scopes a filter to the beat's accounts when the beat carries account_guids; otherwise leaves\n// accounts unset so the store default (all accounts) applies.\nconst accountScope = (accountGuids?: string[]): Pick<TransactionFilter, 'accounts'> =>\n accountGuids?.length ? { accounts: accountGuids } : {}\n\n// Low Account Balance — `drilldown_data` names one account and a window (epoch seconds at UTC\n// midnight, to_date exclusive); only settled, visible rows, a split purchase as its parts, as the\n// pulse drawer listed them. Fails closed: a drawer that cannot name its rows shows none.\nconst accountWindowStrategy: FilterStrategy = ({ beat }) => {\n const { account_guid, from_date, to_date } = beat.insights_payload?.drilldown_data ?? {}\n if (\n typeof account_guid !== 'string' ||\n typeof from_date !== 'number' ||\n typeof to_date !== 'number' ||\n !Number.isFinite(from_date) ||\n !Number.isFinite(to_date) ||\n to_date <= from_date\n ) {\n return { accounts: [], custom: () => false }\n }\n\n return {\n accounts: [account_guid],\n custom: (trn) => !trn.is_hidden && trn.feed_status !== TransactionStatus.PENDING,\n showSplits: true,\n dateRange: { start: fromUnixUTC(from_date), end: fromUnixUTC(to_date - 1) },\n }\n}\n\n// Upcoming Bill: each `drilldown_data` fact is narrowed (utils.ts) and a missing or mistyped one is\n// dropped. A bad due date, amount or guid list draws no chart and lists no rows.\n\nexport const resolveUpcomingBillSlotData: SlotDataResolver = (payload) => {\n const data = payload?.drilldown_data ?? {}\n\n return {\n transaction_guids: guidListOf(data.transaction_guids),\n date: calendarDateOf(data.due_date),\n amount: finiteNumberOf(data.amount),\n category_guid: nonEmptyStringOf(data.category_guid),\n average_amount: finiteNumberOf(data.average_amount),\n merchant_name: nonEmptyStringOf(data.merchant_name),\n }\n}\n\n// Savings Account Balances Summary: the drawer's bars, trend and accounts come from the beat's\n// drilldown_data alone (tupac SavingsAccountBalancesSummary::Factory.drilldown_data), each narrowed:\n// a missing or malformed fact is dropped, so the drawer draws no bars and lists no rows.\nconst dataSeriesOf = (value: unknown): DataSeries | undefined => {\n const series = value as DataSeries | undefined\n return Array.isArray(series?.names) && Array.isArray(series?.data) ? series : undefined\n}\n\nexport const resolveSavingsContributionsSlotData: SlotDataResolver = (payload) => {\n const data = payload?.drilldown_data ?? {}\n\n return {\n account_guids: guidListOf(data.account_guids),\n data_series: dataSeriesOf(data.data_series),\n average_amount: finiteNumberOf(data.average_amount),\n }\n}\n\n// Upcoming Bill: the paid bills plus the projected bill (due date, amount) appended last, so the\n// bars and the projected tab's \"Projected amount\" row read the one list.\nexport const resolveUpcomingBillTransactions: BeatTransactionsResolver = (\n transactions,\n slotData,\n) => {\n const projected = buildProjectedBillTransaction(transactions, slotData)\n\n return projected ? [...transactions, projected] : transactions\n}\n\nconst upcomingBillTipSection: DrillDownVariantConfig['tipSection'] = (\n setCloseTipSection,\n copy,\n _merchantName,\n slotData,\n) => {\n const merchantName = slotData?.merchant_name\n const dueDate = slotData?.date\n if (!merchantName || !dueDate) return undefined\n\n // No hard-coded English: no tip until the copy service carries the message.\n const message = copy.transaction_history_block?.upcoming_bill_tip_message\n if (!message) return undefined\n\n return {\n message: buildDynamicCopy(\n message,\n <strong>{copy.general.tip_section_title}</strong>,\n merchantName,\n formatDate(parseISO(dueDate), DATE_FORMATS_INTL.FULL_MONTH_DAY),\n ) as ReactNode,\n onDismiss: () => setCloseTipSection(true),\n }\n}\n\n// ── Single-bar filter strategies (this MR) ────────────────────────────────────────────────────\n\n// BillAmountNotStandard / UpcomingBill — the series carries a transaction (TRN) guid; match a single\n// transaction by guid within its month.\nconst transactionGuidStrategy: FilterStrategy = ({\n beatTransactions,\n dataSeries,\n selectedIndex,\n}) => {\n const seriesItem = dataSeries[selectedIndex]\n const seriesGuid = seriesItem?.guid\n const hasBeenSplit = beatTransactions.find((trn) => trn.guid === seriesGuid)?.has_been_split\n\n return {\n custom: (trn) => trn.guid === seriesGuid,\n dateRange: monthRange(seriesItem?.date),\n showSplits: !hasBeenSplit,\n }\n}\n\n// CategorySpendingV2 — match transactions in the beat's category (or whose top-level category is that\n// guid) within the month.\nconst categoryStrategy: FilterStrategy = ({ dataSeries, selectedIndex, instanceSlotData }) => {\n const seriesItem = dataSeries[selectedIndex]\n const categoryGuid = instanceSlotData.category_guid ?? seriesItem?.guid\n\n return {\n custom: (trn) =>\n trn.category_guid === categoryGuid || trn.top_level_category_guid === categoryGuid,\n dateRange: monthRange(seriesItem?.date),\n }\n}\n\n// MonthlySpendComparisonV2 — no per-bar guid; show all spending-category transactions\n// (income/transfers/investments excluded via spendCategoryGuids) within the month.\nconst spendCategoriesStrategy: FilterStrategy = ({\n dataSeries,\n selectedIndex,\n spendCategoryGuids,\n instanceSlotData,\n}) => {\n const seriesItem = dataSeries[selectedIndex]\n\n return {\n custom: (trn) =>\n !!trn.top_level_category_guid && spendCategoryGuids.includes(trn.top_level_category_guid),\n dateRange: monthRange(seriesItem?.date),\n ...accountScope(instanceSlotData.account_guids),\n }\n}\n\n// The beat's creation: tupac sends epoch seconds on the envelope and an ISO string on the wrapper.\nconst toCreatedAt = (createdAt: Beat['created_at']) =>\n typeof createdAt === 'number' ? fromUnixTime(createdAt) : parseISO(createdAt ?? '')\n\n// SavingsAccountBalancesSummary — monthly savings contributions, not categorized spend: the beat's\n// savings accounts within the month. The most recent month ends at the beat's creation (the partial\n// month it was generated in); an older month, or a beat created after its last month ended, keeps the\n// whole month. The accounts are drilldown_data's (resolveSavingsContributionsSlotData). Fails closed:\n// a drawer whose drilldown_data names no account lists no rows, never every account.\nconst accountDateStrategy: FilterStrategy = ({\n beat,\n dataSeries,\n selectedIndex,\n isLastTab,\n instanceSlotData,\n}) => {\n const seriesItem = dataSeries[selectedIndex]\n const { start, end } = monthRange(seriesItem?.date)\n const createdAt = toCreatedAt(beat.created_at)\n const accountGuids = instanceSlotData.account_guids\n const dateRange = {\n start,\n end: isLastTab && isValid(createdAt) && createdAt < end ? createdAt : end,\n }\n\n if (!accountGuids?.length) return { dateRange, accounts: [], custom: () => false }\n\n return { dateRange, accounts: accountGuids }\n}\n\n// A row's stored guids within the row's month, scoped to the beat's accounts. parseISO reads the\n// 'yyyy-MM-dd' date as a local calendar day, so the month never shifts with the viewer's time zone.\n// Fails closed: a missing row, guid list or date matches nothing, never the store's displayed range.\nconst guidsWithinMonth = (\n guids: string[] | undefined,\n date: string | undefined,\n accountGuids?: string[],\n): TransactionFilter => {\n const guidSet = new Set(guids)\n const month = parseISO(date ?? '')\n if (!guidSet.size || !isValid(month)) return { accounts: [], custom: () => false }\n\n return {\n custom: (trn) => guidSet.has(trn.guid),\n dateRange: { start: startOfMonth(month), end: endOfMonth(month) },\n ...accountScope(accountGuids),\n }\n}\n\n// Monthly Subscription Aggregate — one data_series row per month, each naming its own\n// aggregated_guids; the selected month tab lists exactly those.\nconst monthlyAggregatedGuidsStrategy: FilterStrategy = ({ beat, dataSeries, selectedIndex }) =>\n guidsWithinMonth(\n dataSeries[selectedIndex]?.aggregated_guids,\n dataSeries[selectedIndex]?.date,\n beat.account_guids,\n )\n\n// ── Double-bar strategies ──────────────────────────────────────────────────────────────────────\n\n// Monthly Spend to Income Comparison — the selected month's main (Spend, sub-tab 0) or comparison\n// (Income, sub-tab 1) bar guids. The V2 payload carries no account_guids on instance_slot.data.\nconst comparisonGuidsStrategy: FilterStrategy = ({\n beat,\n comparisonBars,\n selectedIndex,\n subIndex,\n}) => {\n const bar = comparisonBars[selectedIndex]\n\n return guidsWithinMonth(\n subIndex === 1 ? bar?.comparisonGuids : bar?.mainGuids,\n bar?.date,\n beat.account_guids,\n )\n}\n\n// ── Tabs-only strategies ───────────────────────────────────────────────────────────────────────\n\n// Monthly Non-Essential Category Summary — the series is the two sides of one month (non-essential /\n// essential), not a time series; the selected sub-tab picks the row.\nconst aggregatedGuidsStrategy: FilterStrategy = ({ beat, dataSeries, subIndex }) =>\n guidsWithinMonth(\n dataSeries[subIndex]?.aggregated_guids,\n dataSeries[subIndex]?.date,\n beat.account_guids,\n )\n\nexport const layout: Partial<Record<DrillDownVariant, DrillDownVariantConfig>> = {\n [DRILLDOWN_VARIANTS.TRANSACTION]: {\n top: 'singleBar',\n scope: 'store',\n filterMode: 'store',\n filterStrategy: transactionGuidStrategy,\n colors: (theme: Theme, categoryGuid?: string) => [categoryColor(theme, categoryGuid)],\n header: (ctx: DrillDownHeaderContext) => {\n const { title, subHeader } = ctx\n return {\n heading: title,\n subHeadingText: subHeader?.text || undefined,\n icon: (\n <MerchantLogo\n alt={subHeader?.text}\n categoryGuid={subHeader?.logo?.category_guid || ''}\n merchantGuid={subHeader?.logo?.merchant_guid || ''}\n size={16}\n />\n ),\n }\n },\n tipSection: (setCloseTipSection, copy, merchantName = '') => {\n return {\n message: buildDynamicCopy(\n copy.general.tip_section_message,\n <strong>{copy.general.tip_section_title}</strong>,\n <strong>{merchantName}</strong>,\n ),\n onDismiss: () => setCloseTipSection(true),\n } as TipSectionProps\n },\n },\n [DRILLDOWN_VARIANTS.CATEGORY_SPEND]: {\n top: 'singleBar',\n filterStrategy: categoryStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme, categoryGuid?: string) => [categoryColor(theme, categoryGuid)],\n header: (ctx: DrillDownHeaderContext) => {\n return {\n heading: ctx.categoryName,\n subHeadingText: ctx.selectedDate\n ? formatDate(toLocalDate(ctx.selectedDate), DATE_FORMATS_INTL.MONTH_LONG) || ''\n : '',\n icon: <Icon name=\"calendar_month\" size={16} />,\n }\n },\n },\n [DRILLDOWN_VARIANTS.MONTHLY_SPEND]: {\n top: 'singleBar',\n filterStrategy: spendCategoriesStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.chart.chart2 ?? theme.palette.primary.main],\n },\n [DRILLDOWN_VARIANTS.ACCOUNT_BALANCE]: {\n top: 'singleBar',\n filterStrategy: accountDateStrategy,\n slotData: resolveSavingsContributionsSlotData,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.success.main],\n },\n // Upcoming Bill: one bar per paid bill plus the projected bill, all from `drilldown_data`; a bar's\n // tab lists that bill alone (filtered locally, as legacy pulse does).\n [DRILLDOWN_VARIANTS.UPCOMING_BILL]: {\n top: 'upcomingBill',\n filterStrategy: transactionGuidStrategy,\n scope: 'beat',\n filterMode: 'local',\n colors: (theme: Theme, categoryGuid?: string) => [categoryColor(theme, categoryGuid)],\n slotData: resolveUpcomingBillSlotData,\n beatTransactions: resolveUpcomingBillTransactions,\n fetchMissingTransactions: (beat) => beat.insights_payload?.drilldown_data?.transaction_guids,\n tipSection: upcomingBillTipSection,\n },\n\n // Low Account Balance's \"Transactions\" link: the account's transactions for the alert's window.\n [DRILLDOWN_VARIANTS.LOW_BALANCE]: {\n top: 'list',\n filterStrategy: accountWindowStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.primary.main],\n // Figma 438:27640 lists the rows straight under the drawer header.\n hideListHeader: true,\n },\n\n // Monthly Subscription Aggregate's \"View Transactions\": one tab per month, no chart, each month\n // listing its own aggregated_guids.\n [DRILLDOWN_VARIANTS.SUBSCRIPTION_AGGREGATE]: {\n top: 'monthTabs',\n filterStrategy: monthlyAggregatedGuidsStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.primary.main],\n },\n\n // ── Double-bar comparison beats (month tabs + main / comparison sub-tabs) ────────────────────\n // Monthly Spend to Income Comparison: Spend vs Income per month, in the card block's palette.\n [DRILLDOWN_VARIANTS.SPEND_TO_INCOME]: {\n top: 'doubleBar',\n filterStrategy: comparisonGuidsStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => getDoubleBarLayoutConfig(theme, 'spend_to_income')?.color ?? [],\n subTabAriaLabels: (copy) => [copy.general.spend_transactions, copy.general.income_transactions],\n },\n // Cost of Living to Income: one living-cost category (Housing, Groceries, ...) vs Income per month.\n // The cost bar takes the beat category's colour and Income the legacy chart4, as the pulse drawer.\n [DRILLDOWN_VARIANTS.COST_OF_LIVING]: {\n top: 'doubleBar',\n filterStrategy: comparisonGuidsStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme, categoryGuid?: string) => [\n categoryColor(theme, categoryGuid),\n theme.palette.chart.chart4 ?? theme.palette.primary.main,\n ],\n subTabAriaLabels: (copy) => [undefined, copy.general.income_transactions],\n },\n // NEXT MR — the other double-bar beats reuse comparisonGuidsStrategy with their own palettes:\n // [DRILLDOWN_VARIANTS.SAVINGS_TO_INCOME]: comparison('savingsVsIncome'),\n // [DRILLDOWN_VARIANTS.DEBT_TO_INCOME]: comparison('debtVsIncome'),\n\n // ── Tabs-only beats (no chart; one side tab per series row) ─────────────────────────────────\n // Monthly Non-Essential Category Summary: the donut card's \"View more\" lists one month's spend by\n // side, Non-essential / Essential, each side its own aggregated_guids. The tabs are labelled by\n // the rows' pre-localized labels, in wire order; a row without one gets no tab.\n [DRILLDOWN_VARIANTS.NON_ESSENTIAL_CATEGORY]: {\n top: 'tabsOnly',\n filterStrategy: aggregatedGuidsStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme) => [theme.palette.primary.main],\n },\n // NEXT MR — [DRILLDOWN_VARIANTS.DEPOSIT_SUMMARY] (WEC-2097, MR !1756).\n}\n\n// Fallback for a missing/typo'd backend variant: a single-bar, store-scoped drawer filtered by the\n// beat's transaction guid and colored by category, so the drawer still renders instead of crashing.\nexport const FALLBACK: DrillDownVariantConfig = {\n top: 'singleBar',\n filterStrategy: transactionGuidStrategy,\n scope: 'store',\n filterMode: 'store',\n colors: (theme: Theme, categoryGuid?: string) => [categoryColor(theme, categoryGuid)],\n}\n"],"mappings":";;;;;;;;;;;AAiDA,IAAa,IAAqB;AAAA,EAChC,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,wBAAwB;AAAA,EACxB,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,wBAAwB;AAC1B,GAqEM,IAAA,CAAiB,GAAc,MAClC,KAAgB,EAAiB,GAAc,CAAK,KAAM,EAAM,QAAQ,QAAQ,MAE7E,IAAA,CAAe,MACnB,OAAO,KAAS,WAAW,EAAS,CAAI,IAAI,IAAI,KAAK,KAAQ,GAAG,GAE5D,IAAA,CAAc,OAA8C;AAAA,EAChE,OAAO,EAAa,EAAY,CAAI,CAAC;AAAA,EACrC,KAAK,EAAW,EAAY,CAAI,CAAC;AACnC,IAIM,IAAA,CAAgB,MACpB,GAAc,SAAS,EAAE,UAAU,EAAa,IAAI,CAAC,GAKjD,IAAA,CAAyC,EAAE,MAAA,EAAA,MAAW;AAC1D,QAAM,EAAE,cAAA,GAAc,WAAA,GAAW,SAAA,EAAA,IAAY,EAAK,kBAAkB,kBAAkB,CAAC;AACvF,SACE,OAAO,KAAiB,YACxB,OAAO,KAAc,YACrB,OAAO,KAAY,YACnB,CAAC,OAAO,SAAS,CAAS,KAC1B,CAAC,OAAO,SAAS,CAAO,KACxB,KAAW,IAEJ;AAAA,IAAE,UAAU,CAAC;AAAA,IAAG,QAAA,MAAc;AAAA,EAAM,IAGtC;AAAA,IACL,UAAU,CAAC,CAAY;AAAA,IACvB,QAAA,CAAS,MAAQ,CAAC,EAAI,aAAa,EAAI,gBAAgB,EAAkB;AAAA,IACzE,YAAY;AAAA,IACZ,WAAW;AAAA,MAAE,OAAO,EAAY,CAAS;AAAA,MAAG,KAAK,EAAY,IAAU,CAAC;AAAA,IAAE;AAAA,EAC5E;AACF,GAKa,IAAA,CAAiD,MAAY;AACxE,QAAM,IAAO,GAAS,kBAAkB,CAAC;AAEzC,SAAO;AAAA,IACL,mBAAmB,EAAW,EAAK,iBAAiB;AAAA,IACpD,MAAM,EAAe,EAAK,QAAQ;AAAA,IAClC,QAAQ,EAAe,EAAK,MAAM;AAAA,IAClC,eAAe,EAAiB,EAAK,aAAa;AAAA,IAClD,gBAAgB,EAAe,EAAK,cAAc;AAAA,IAClD,eAAe,EAAiB,EAAK,aAAa;AAAA,EACpD;AACF,GAKM,IAAA,CAAgB,MAA2C;AAC/D,QAAM,IAAS;AACf,SAAO,MAAM,QAAQ,GAAQ,KAAK,KAAK,MAAM,QAAQ,GAAQ,IAAI,IAAI,IAAS;AAChF,GAEa,IAAA,CAAyD,MAAY;AAChF,QAAM,IAAO,GAAS,kBAAkB,CAAC;AAEzC,SAAO;AAAA,IACL,eAAe,EAAW,EAAK,aAAa;AAAA,IAC5C,aAAa,EAAa,EAAK,WAAW;AAAA,IAC1C,gBAAgB,EAAe,EAAK,cAAc;AAAA,EACpD;AACF,GAIa,IAAA,CACX,GACA,MACG;AACH,QAAM,IAAY,EAA8B,GAAc,CAAQ;AAEtE,SAAO,IAAY,CAAC,GAAG,GAAc,CAAS,IAAI;AACpD,GAEM,IAAA,CACJ,GACA,GACA,GACA,MACG;AACH,QAAM,IAAe,GAAU,eACzB,IAAU,GAAU;AAC1B,MAAI,CAAC,KAAgB,CAAC,EAAS;AAG/B,QAAM,IAAU,EAAK,2BAA2B;AAChD,MAAK;AAEL,WAAO;AAAA,MACL,SAAS,EACP,GACA,gBAAA,EAAC,UAAD,EAAA,UAAS,EAAK,QAAQ,kBAA0B,CAAA,GAChD,GACA,EAAW,EAAS,CAAO,GAAG,EAAkB,cAAc,CAChE;AAAA,MACA,WAAA,MAAiB,EAAmB,EAAI;AAAA,IAC1C;AACF,GAMM,IAAA,CAA2C,EAC/C,kBAAA,GACA,YAAA,GACA,eAAA,EAAA,MACI;AACJ,QAAM,IAAa,EAAW,CAAA,GACxB,IAAa,GAAY,MACzB,IAAe,EAAiB,KAAA,CAAM,MAAQ,EAAI,SAAS,CAAU,GAAG;AAE9E,SAAO;AAAA,IACL,QAAA,CAAS,MAAQ,EAAI,SAAS;AAAA,IAC9B,WAAW,EAAW,GAAY,IAAI;AAAA,IACtC,YAAY,CAAC;AAAA,EACf;AACF,GAIM,IAAA,CAAoC,EAAE,YAAA,GAAY,eAAA,GAAe,kBAAA,EAAA,MAAuB;AAC5F,QAAM,IAAa,EAAW,CAAA,GACxB,IAAe,EAAiB,iBAAiB,GAAY;AAEnE,SAAO;AAAA,IACL,QAAA,CAAS,MACP,EAAI,kBAAkB,KAAgB,EAAI,4BAA4B;AAAA,IACxE,WAAW,EAAW,GAAY,IAAI;AAAA,EACxC;AACF,GAIM,IAAA,CAA2C,EAC/C,YAAA,GACA,eAAA,GACA,oBAAA,GACA,kBAAA,EAAA,MACI;AACJ,QAAM,IAAa,EAAW,CAAA;AAE9B,SAAO;AAAA,IACL,QAAA,CAAS,MACP,CAAC,CAAC,EAAI,2BAA2B,EAAmB,SAAS,EAAI,uBAAuB;AAAA,IAC1F,WAAW,EAAW,GAAY,IAAI;AAAA,IACtC,GAAG,EAAa,EAAiB,aAAa;AAAA,EAChD;AACF,GAGM,IAAA,CAAe,MACnB,OAAO,KAAc,WAAW,EAAa,CAAS,IAAI,EAAS,KAAa,EAAE,GAO9E,IAAA,CAAuC,EAC3C,MAAA,GACA,YAAA,GACA,eAAA,GACA,WAAA,GACA,kBAAA,EAAA,MACI;AACJ,QAAM,IAAa,EAAW,CAAA,GACxB,EAAE,OAAA,GAAO,KAAA,EAAA,IAAQ,EAAW,GAAY,IAAI,GAC5C,IAAY,EAAY,EAAK,UAAU,GACvC,IAAe,EAAiB,eAChC,IAAY;AAAA,IAChB,OAAA;AAAA,IACA,KAAK,KAAa,EAAQ,CAAS,KAAK,IAAY,IAAM,IAAY;AAAA,EACxE;AAEA,SAAK,GAAc,SAEZ;AAAA,IAAE,WAAA;AAAA,IAAW,UAAU;AAAA,EAAa,IAFT;AAAA,IAAE,WAAA;AAAA,IAAW,UAAU,CAAC;AAAA,IAAG,QAAA,MAAc;AAAA,EAAM;AAGnF,GAKM,IAAA,CACJ,GACA,GACA,MACsB;AACtB,QAAM,IAAU,IAAI,IAAI,CAAK,GACvB,IAAQ,EAAS,KAAQ,EAAE;AACjC,SAAI,CAAC,EAAQ,QAAQ,CAAC,EAAQ,CAAK,IAAU;AAAA,IAAE,UAAU,CAAC;AAAA,IAAG,QAAA,MAAc;AAAA,EAAM,IAE1E;AAAA,IACL,QAAA,CAAS,MAAQ,EAAQ,IAAI,EAAI,IAAI;AAAA,IACrC,WAAW;AAAA,MAAE,OAAO,EAAa,CAAK;AAAA,MAAG,KAAK,EAAW,CAAK;AAAA,IAAE;AAAA,IAChE,GAAG,EAAa,CAAY;AAAA,EAC9B;AACF,GAIM,IAAA,CAAkD,EAAE,MAAA,GAAM,YAAA,GAAY,eAAA,EAAA,MAC1E,EACE,EAAW,CAAA,GAAgB,kBAC3B,EAAW,CAAA,GAAgB,MAC3B,EAAK,aACP,GAMI,IAAA,CAA2C,EAC/C,MAAA,GACA,gBAAA,GACA,eAAA,GACA,UAAA,EAAA,MACI;AACJ,QAAM,IAAM,EAAe,CAAA;AAE3B,SAAO,EACL,MAAa,IAAI,GAAK,kBAAkB,GAAK,WAC7C,GAAK,MACL,EAAK,aACP;AACF,GAMM,IAAA,CAA2C,EAAE,MAAA,GAAM,YAAA,GAAY,UAAA,EAAA,MACnE,EACE,EAAW,CAAA,GAAW,kBACtB,EAAW,CAAA,GAAW,MACtB,EAAK,aACP,GAEW,KAAoE;AAAA,EAC9E,CAAA,EAAmB,WAAA,GAAc;AAAA,IAChC,KAAK;AAAA,IACL,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,QAAA,CAAS,GAAc,MAA0B,CAAC,EAAc,GAAO,CAAY,CAAC;AAAA,IACpF,QAAA,CAAS,MAAgC;AACvC,YAAM,EAAE,OAAA,GAAO,WAAA,EAAA,IAAc;AAC7B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,gBAAgB,GAAW,QAAQ;AAAA,QACnC,MACE,gBAAA,EAAC,GAAD;AAAA,UACE,KAAK,GAAW;AAAA,UAChB,cAAc,GAAW,MAAM,iBAAiB;AAAA,UAChD,cAAc,GAAW,MAAM,iBAAiB;AAAA,UAChD,MAAM;AAAA,QACP,CAAA;AAAA,MAEL;AAAA,IACF;AAAA,IACA,YAAA,CAAa,GAAoB,GAAM,IAAe,QAC7C;AAAA,MACL,SAAS,EACP,EAAK,QAAQ,qBACb,gBAAA,EAAC,UAAD,EAAA,UAAS,EAAK,QAAQ,kBAA0B,CAAA,GAChD,gBAAA,EAAC,UAAD,EAAA,UAAS,EAAqB,CAAA,CAChC;AAAA,MACA,WAAA,MAAiB,EAAmB,EAAI;AAAA,IAC1C;AAAA,EAEJ;AAAA,EACC,CAAA,EAAmB,cAAA,GAAiB;AAAA,IACnC,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,GAAc,MAA0B,CAAC,EAAc,GAAO,CAAY,CAAC;AAAA,IACpF,QAAA,CAAS,OACA;AAAA,MACL,SAAS,EAAI;AAAA,MACb,gBAAgB,EAAI,gBAChB,EAAW,EAAY,EAAI,YAAY,GAAG,EAAkB,UAAU,KAAK;AAAA,MAE/E,MAAM,gBAAA,EAAC,GAAD;AAAA,QAAM,MAAK;AAAA,QAAiB,MAAM;AAAA,MAAK,CAAA;AAAA,IAC/C;AAAA,EAEJ;AAAA,EACC,CAAA,EAAmB,aAAA,GAAgB;AAAA,IAClC,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,MAAiB,CAAC,EAAM,QAAQ,MAAM,UAAU,EAAM,QAAQ,QAAQ,IAAI;AAAA,EACrF;AAAA,EACC,CAAA,EAAmB,eAAA,GAAkB;AAAA,IACpC,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,UAAU;AAAA,IACV,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,MAAiB,CAAC,EAAM,QAAQ,QAAQ,IAAI;AAAA,EACvD;AAAA,EAGC,CAAA,EAAmB,aAAA,GAAgB;AAAA,IAClC,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,GAAc,MAA0B,CAAC,EAAc,GAAO,CAAY,CAAC;AAAA,IACpF,UAAU;AAAA,IACV,kBAAkB;AAAA,IAClB,0BAAA,CAA2B,MAAS,EAAK,kBAAkB,gBAAgB;AAAA,IAC3E,YAAY;AAAA,EACd;AAAA,EAGC,CAAA,EAAmB,WAAA,GAAc;AAAA,IAChC,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,MAAiB,CAAC,EAAM,QAAQ,QAAQ,IAAI;AAAA,IAErD,gBAAgB;AAAA,EAClB;AAAA,EAIC,CAAA,EAAmB,sBAAA,GAAyB;AAAA,IAC3C,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,MAAiB,CAAC,EAAM,QAAQ,QAAQ,IAAI;AAAA,EACvD;AAAA,EAIC,CAAA,EAAmB,eAAA,GAAkB;AAAA,IACpC,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,MAAiB,EAAyB,GAAO,iBAAiB,GAAG,SAAS,CAAC;AAAA,IACxF,kBAAA,CAAmB,MAAS,CAAC,EAAK,QAAQ,oBAAoB,EAAK,QAAQ,mBAAmB;AAAA,EAChG;AAAA,EAGC,CAAA,EAAmB,cAAA,GAAiB;AAAA,IACnC,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,GAAc,MAA0B,CAC/C,EAAc,GAAO,CAAY,GACjC,EAAM,QAAQ,MAAM,UAAU,EAAM,QAAQ,QAAQ,IACtD;AAAA,IACA,kBAAA,CAAmB,MAAS,CAAC,QAAW,EAAK,QAAQ,mBAAmB;AAAA,EAC1E;AAAA,EASC,CAAA,EAAmB,sBAAA,GAAyB;AAAA,IAC3C,KAAK;AAAA,IACL,gBAAgB;AAAA,IAChB,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAA,CAAS,MAAiB,CAAC,EAAM,QAAQ,QAAQ,IAAI;AAAA,EACvD;AAEF,GAIa,KAAmC;AAAA,EAC9C,KAAK;AAAA,EACL,gBAAgB;AAAA,EAChB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,QAAA,CAAS,GAAc,MAA0B,CAAC,EAAc,GAAO,CAAY,CAAC;AACtF"}
|