@mx-cartographer/experiences 9.7.105 → 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 +9 -0
- package/dist/core/types/localization/InsightsFeedCopy.d.ts +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/dist/insights-v2/actionBlocks/TransactionListBlock/layout.d.ts +3 -5
- package/dist/insights-v2/actionBlocks/TransactionListBlock/layout.es.js +41 -36
- package/dist/insights-v2/actionBlocks/TransactionListBlock/layout.es.js.map +1 -1
- package/dist/insights-v2/blocks/ComponentBlocks/TransactionBlock/TransactionBlock.es.js +86 -85
- package/dist/insights-v2/blocks/ComponentBlocks/TransactionBlock/TransactionBlock.es.js.map +1 -1
- package/dist/insights-v2/blocks/ComponentBlocks/TransactionBlock/layout.d.ts +4 -0
- package/dist/insights-v2/blocks/ComponentBlocks/TransactionBlock/layout.es.js +12 -6
- package/dist/insights-v2/blocks/ComponentBlocks/TransactionBlock/layout.es.js.map +1 -1
- package/package.json +1 -1
|
@@ -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":"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"}
|
|
@@ -7,13 +7,11 @@ export declare const TRANSACTION_LIST_VARIANTS: {
|
|
|
7
7
|
readonly GENERIC_LIST: "generic_list";
|
|
8
8
|
/** Opens the list under the beat's title, description and "Learn More". */
|
|
9
9
|
readonly HELP_HEADER: "help_header";
|
|
10
|
+
/** Opens Micro Deposits' Deposit History list. */
|
|
11
|
+
readonly DEPOSIT_HISTORY: "deposit_history";
|
|
10
12
|
};
|
|
11
13
|
export declare const HELP_HEADER_VARIANT: "help_header";
|
|
12
|
-
/**
|
|
13
|
-
* Drawer title: the variant's title when it has one (`generic_list` and `help_header` ->
|
|
14
|
-
* "Transactions"; the help header repeats the beat's title inside the drawer), otherwise the
|
|
15
|
-
* beat's own localized title (`summary_header`, `standard`, or no variant).
|
|
16
|
-
*/
|
|
14
|
+
/** Drawer title: the variant's title when it has one, otherwise the beat's own title. Add new variants here. */
|
|
17
15
|
export declare const getTransactionListDrawerTitle: (variant: string | null | undefined, copy: GlobalCopy, beat?: Beat) => string;
|
|
18
16
|
export interface HelpHeaderContent {
|
|
19
17
|
/** Pre-localized HTML under the title, from the payload's `drilldown_data.description_html`. */
|
|
@@ -1,61 +1,66 @@
|
|
|
1
1
|
import { readHelp as m, toText as o } from "../HelpBlock/layout.es.js";
|
|
2
2
|
import { parseISO as c } from "date-fns/parseISO";
|
|
3
|
-
var
|
|
3
|
+
var n = {
|
|
4
4
|
SUMMARY_HEADER: "summary_header",
|
|
5
5
|
GENERIC_LIST: "generic_list",
|
|
6
|
-
HELP_HEADER: "help_header"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
HELP_HEADER: "help_header",
|
|
7
|
+
DEPOSIT_HISTORY: "deposit_history"
|
|
8
|
+
}, E = n.HELP_HEADER, h = (t, e, r) => {
|
|
9
|
+
switch (t) {
|
|
10
|
+
case n.GENERIC_LIST:
|
|
11
|
+
case n.HELP_HEADER:
|
|
12
|
+
return e.transactions?.transactions_title ?? "";
|
|
13
|
+
case n.DEPOSIT_HISTORY:
|
|
14
|
+
return e.insights_feed?.transaction_list_block?.deposit_history_drawer_title ?? "";
|
|
15
|
+
default:
|
|
16
|
+
return r?.insights_payload?.title ?? "";
|
|
17
|
+
}
|
|
18
|
+
}, A = (t, e) => {
|
|
14
19
|
if (t?.variant !== E) return;
|
|
15
|
-
const
|
|
20
|
+
const r = o(e?.help_label), a = m(e);
|
|
16
21
|
return {
|
|
17
|
-
descriptionHtml: o(
|
|
18
|
-
...
|
|
19
|
-
...
|
|
20
|
-
label:
|
|
22
|
+
descriptionHtml: o(e?.description_html),
|
|
23
|
+
...r && a && { learnMore: {
|
|
24
|
+
...a,
|
|
25
|
+
label: r
|
|
21
26
|
} }
|
|
22
27
|
};
|
|
23
|
-
},
|
|
24
|
-
s.has(t) || (s.add(t), console.warn(
|
|
25
|
-
}, d = (t,
|
|
28
|
+
}, f = (t) => typeof t == "number" && Number.isFinite(t) ? t : void 0, p = (t) => typeof t == "number" && Number.isInteger(t) && t > 0 ? t : void 0, l = Date.UTC(2e3, 0, 1), u = Date.UTC(2100, 0, 1), s = /* @__PURE__ */ new WeakSet(), _ = (t, e) => {
|
|
29
|
+
s.has(t) || (s.add(t), console.warn(e));
|
|
30
|
+
}, d = (t, e, r) => {
|
|
26
31
|
if (t == null || t === "") return;
|
|
27
|
-
let
|
|
28
|
-
if (typeof t == "number" && Number.isFinite(t))
|
|
32
|
+
let a;
|
|
33
|
+
if (typeof t == "number" && Number.isFinite(t)) a = t < 1e12 ? t * 1e3 : t;
|
|
29
34
|
else if (typeof t == "string") {
|
|
30
35
|
const i = c(t).getTime();
|
|
31
|
-
|
|
36
|
+
a = Number.isNaN(i) ? void 0 : i;
|
|
32
37
|
}
|
|
33
|
-
if (
|
|
34
|
-
_(
|
|
38
|
+
if (a === void 0 || a < l || a > u) {
|
|
39
|
+
_(r, `[TransactionListBlock] drilldown_data.${e} is unparseable or out of range (${typeof t})`);
|
|
35
40
|
return;
|
|
36
41
|
}
|
|
37
|
-
return new Date(
|
|
42
|
+
return new Date(a);
|
|
38
43
|
}, D = (t) => {
|
|
39
|
-
if (t?.variant !==
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
Object.keys(
|
|
44
|
+
if (t?.variant !== n.SUMMARY_HEADER) return;
|
|
45
|
+
const e = t?.drilldown_data;
|
|
46
|
+
if (!e) return;
|
|
47
|
+
const r = f(e.amount);
|
|
48
|
+
if (r === void 0) {
|
|
49
|
+
Object.keys(e).length > 0 && _(e, "[TransactionListBlock] drilldown_data has no finite `amount` — no summary header.");
|
|
45
50
|
return;
|
|
46
51
|
}
|
|
47
52
|
return {
|
|
48
|
-
amount:
|
|
49
|
-
startDate: d(
|
|
50
|
-
endDate: d(
|
|
51
|
-
count: p(
|
|
53
|
+
amount: r,
|
|
54
|
+
startDate: d(e.start_date, "start_date", e),
|
|
55
|
+
endDate: d(e.end_date, "end_date", e),
|
|
56
|
+
count: p(e.count)
|
|
52
57
|
};
|
|
53
58
|
};
|
|
54
59
|
export {
|
|
55
60
|
E as HELP_HEADER_VARIANT,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
n as TRANSACTION_LIST_VARIANTS,
|
|
62
|
+
h as getTransactionListDrawerTitle,
|
|
63
|
+
A as readHelpHeader,
|
|
59
64
|
D as readSummary
|
|
60
65
|
};
|
|
61
66
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionListBlock/layout.ts"],"sourcesContent":["import { parseISO } from 'date-fns/parseISO'\n\nimport type {\n Beat,\n DrilldownData,\n GlobalCopy,\n InDescriptionCTA,\n PrimaryCTA,\n SecondaryCTA,\n} from '../../../core'\nimport { readHelp, toText, type HelpContent } from '../HelpBlock/layout'\n\ntype DrilldownCta = PrimaryCTA | SecondaryCTA | InDescriptionCTA\n\n/** The transaction_list_block CTA variants. */\nexport const TRANSACTION_LIST_VARIANTS = {\n SUMMARY_HEADER: 'summary_header',\n GENERIC_LIST: 'generic_list',\n /** Opens the list under the beat's title, description and \"Learn More\". */\n HELP_HEADER: 'help_header',\n
|
|
1
|
+
{"version":3,"file":"layout.es.js","names":[],"sources":["../../../../src/insights-v2/actionBlocks/TransactionListBlock/layout.ts"],"sourcesContent":["import { parseISO } from 'date-fns/parseISO'\n\nimport type {\n Beat,\n DrilldownData,\n GlobalCopy,\n InDescriptionCTA,\n PrimaryCTA,\n SecondaryCTA,\n} from '../../../core'\nimport { readHelp, toText, type HelpContent } from '../HelpBlock/layout'\n\ntype DrilldownCta = PrimaryCTA | SecondaryCTA | InDescriptionCTA\n\n/** The transaction_list_block CTA variants. */\nexport const TRANSACTION_LIST_VARIANTS = {\n SUMMARY_HEADER: 'summary_header',\n GENERIC_LIST: 'generic_list',\n /** Opens the list under the beat's title, description and \"Learn More\". */\n HELP_HEADER: 'help_header',\n /** Opens Micro Deposits' Deposit History list. */\n DEPOSIT_HISTORY: 'deposit_history',\n} as const\n\nexport const HELP_HEADER_VARIANT = TRANSACTION_LIST_VARIANTS.HELP_HEADER\n\n/** Drawer title: the variant's title when it has one, otherwise the beat's own title. Add new variants here. */\nexport const getTransactionListDrawerTitle = (\n variant: string | null | undefined,\n copy: GlobalCopy,\n beat?: Beat,\n): string => {\n switch (variant) {\n case TRANSACTION_LIST_VARIANTS.GENERIC_LIST:\n case TRANSACTION_LIST_VARIANTS.HELP_HEADER:\n return copy.transactions?.transactions_title ?? ''\n case TRANSACTION_LIST_VARIANTS.DEPOSIT_HISTORY:\n return copy.insights_feed?.transaction_list_block?.deposit_history_drawer_title ?? ''\n default:\n return beat?.insights_payload?.title ?? ''\n }\n}\n\nexport interface HelpHeaderContent {\n /** Pre-localized HTML under the title, from the payload's `drilldown_data.description_html`. */\n descriptionHtml?: string\n /** The \"Learn More\" link and the help drawer it opens; absent when the beat carries no help. */\n learnMore?: HelpContent & { label: string }\n}\n\n/**\n * The help-header variant's text, read from the payload's `drilldown_data` (shared by every CTA\n * of the card): `description_html`, and `help_label` with the `help_title` / `help_html` that\n * `readHelp` reads. Only the `help_header` variant renders the block.\n */\nexport const readHelpHeader = (\n cta: DrilldownCta | undefined,\n drilldown: DrilldownData | undefined,\n): HelpHeaderContent | undefined => {\n if (cta?.variant !== HELP_HEADER_VARIANT) return undefined\n\n const label = toText(drilldown?.help_label)\n const help = readHelp(drilldown)\n\n return {\n descriptionHtml: toText(drilldown?.description_html),\n ...(label && help && { learnMore: { ...help, label } }),\n }\n}\n\nexport interface TransactionListSummary {\n /** Headline figure (e.g. total withdrawn). */\n amount: number\n /** Validated range endpoints; rendered as `{start} – {end}`. Omitted when the payload value is\n * missing or unparseable (so the drawer never crashes on an Invalid Date). */\n startDate?: Date\n endDate?: Date\n /** Number of transactions for the pill, from the CTA's `drilldown_data.count`. */\n count?: number\n}\n\nconst toFiniteNumber = (value: unknown): number | undefined =>\n typeof value === 'number' && Number.isFinite(value) ? value : undefined\n\n// `> 0`: the card only shows with >= 1 guid, so a `count` of 0 is dropped and falls back to the\n// guid count rather than rendering \"0 transactions\".\nconst toCount = (value: unknown): number | undefined =>\n typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined\n\n// Sane epoch window so a bad/typo payload can't render a far-past/future date.\nconst EPOCH_MIN = Date.UTC(2000, 0, 1)\nconst EPOCH_MAX = Date.UTC(2100, 0, 1)\n\n// `readSummary` runs on every observer re-render (no `useMemo` by design), so warn at most once per\n// distinct payload object instead of on every render. Keyed on the `drilldown_data` object, which is\n// a stable reference across re-renders of the same beat.\nconst warnedPayloads = new WeakSet<object>()\nconst warnOncePerPayload = (source: object, message: string): void => {\n if (warnedPayloads.has(source)) return\n warnedPayloads.add(source)\n console.warn(message)\n}\n\n/**\n * Parse a payload date value to a validated `Date`, or `undefined`. Accepts unix seconds or\n * milliseconds (auto-detected) and ISO strings. Fails closed (no throw): an unparseable or\n * out-of-range value returns undefined and warns, so `formatDate`/`intlFormat` never sees an Invalid\n * Date (which throws `RangeError` and takes down the whole drawer render).\n */\nconst toDate = (value: unknown, field: string, source: object): Date | undefined => {\n // Missing/empty is a normal \"no range\" case — return quietly, don't warn.\n if (value === undefined || value === null || value === '') return undefined\n\n let ms: number | undefined\n if (typeof value === 'number' && Number.isFinite(value)) {\n // < 1e12 is treated as seconds (1e12 ms is year 33658). Trade-off: genuine ms timestamps from\n // ~2000-2001 would be misread as seconds and fall outside the window below (dropped, not shown).\n ms = value < 1e12 ? value * 1000 : value\n } else if (typeof value === 'string') {\n const parsed = parseISO(value).getTime()\n ms = Number.isNaN(parsed) ? undefined : parsed\n }\n\n if (ms === undefined || ms < EPOCH_MIN || ms > EPOCH_MAX) {\n warnOncePerPayload(\n source,\n `[TransactionListBlock] drilldown_data.${field} is unparseable or out of range (${typeof value})`,\n )\n return undefined\n }\n return new Date(ms)\n}\n\n/**\n * Summary-header figures for the drawer, read from the clicked CTA's `drilldown_data` (the backend\n * hangs a drilldown's own data off the CTA that opens it). Fails closed throughout: only the\n * `summary_header` variant renders a header (so figures from a different CTA that `getDynamicCta…`\n * fell back to can't be misattributed), then without a finite `amount` there is still no header, and\n * each date is validated to a real `Date` or dropped — an unparseable value never reaches\n * `formatDate` (which throws on an Invalid Date).\n *\n * `drilldown_data` fields: `amount` (number), `start_date`/`end_date` (unix seconds|ms or ISO),\n * `count` (number of transactions).\n */\nexport const readSummary = (cta: DrilldownCta | undefined): TransactionListSummary | undefined => {\n // Defense-in-depth on financial figures: only the summary-header variant shows the card.\n if (cta?.variant !== TRANSACTION_LIST_VARIANTS.SUMMARY_HEADER) return undefined\n\n const source = cta?.drilldown_data\n if (!source) return undefined\n\n const amount = toFiniteNumber(source.amount)\n if (amount === undefined) {\n // `drilldown_data` present but no usable amount -> surface the likely contract drift (field\n // rename) instead of a silently missing header.\n if (Object.keys(source).length > 0) {\n warnOncePerPayload(\n source,\n '[TransactionListBlock] drilldown_data has no finite `amount` — no summary header.',\n )\n }\n return undefined\n }\n\n return {\n amount,\n startDate: toDate(source.start_date, 'start_date', source),\n endDate: toDate(source.end_date, 'end_date', source),\n count: toCount(source.count),\n }\n}\n"],"mappings":";;AAeA,IAAa,IAA4B;AAAA,EACvC,gBAAgB;AAAA,EAChB,cAAc;AAAA,EAEd,aAAa;AAAA,EAEb,iBAAiB;AACnB,GAEa,IAAsB,EAA0B,aAGhD,IAAA,CACX,GACA,GACA,MACW;AACX,UAAQ,GAAR;AAAA,IACE,KAAK,EAA0B;AAAA,IAC/B,KAAK,EAA0B;AAC7B,aAAO,EAAK,cAAc,sBAAsB;AAAA,IAClD,KAAK,EAA0B;AAC7B,aAAO,EAAK,eAAe,wBAAwB,gCAAgC;AAAA,IACrF;AACE,aAAO,GAAM,kBAAkB,SAAS;AAAA,EAC5C;AACF,GAca,IAAA,CACX,GACA,MACkC;AAClC,MAAI,GAAK,YAAY,EAAqB;AAE1C,QAAM,IAAQ,EAAO,GAAW,UAAU,GACpC,IAAO,EAAS,CAAS;AAE/B,SAAO;AAAA,IACL,iBAAiB,EAAO,GAAW,gBAAgB;AAAA,IACnD,GAAI,KAAS,KAAQ,EAAE,WAAW;AAAA,MAAE,GAAG;AAAA,MAAM,OAAA;AAAA,IAAM,EAAE;AAAA,EACvD;AACF,GAaM,IAAA,CAAkB,MACtB,OAAO,KAAU,YAAY,OAAO,SAAS,CAAK,IAAI,IAAQ,QAI1D,IAAA,CAAW,MACf,OAAO,KAAU,YAAY,OAAO,UAAU,CAAK,KAAK,IAAQ,IAAI,IAAQ,QAGxE,IAAY,KAAK,IAAI,KAAM,GAAG,CAAC,GAC/B,IAAY,KAAK,IAAI,MAAM,GAAG,CAAC,GAK/B,IAAiB,oBAAI,QAAgB,GACrC,IAAA,CAAsB,GAAgB,MAA0B;AACpE,EAAI,EAAe,IAAI,CAAM,MAC7B,EAAe,IAAI,CAAM,GACzB,QAAQ,KAAK,CAAO;AACtB,GAQM,IAAA,CAAU,GAAgB,GAAe,MAAqC;AAElF,MAA2B,KAAU,QAAQ,MAAU,GAAI;AAE3D,MAAI;AACJ,MAAI,OAAO,KAAU,YAAY,OAAO,SAAS,CAAK,EAGpD,CAAA,IAAK,IAAQ,OAAO,IAAQ,MAAO;AAAA,WAC1B,OAAO,KAAU,UAAU;AACpC,UAAM,IAAS,EAAS,CAAK,EAAE,QAAQ;AACvC,IAAA,IAAK,OAAO,MAAM,CAAM,IAAI,SAAY;AAAA,EAC1C;AAEA,MAAI,MAAO,UAAa,IAAK,KAAa,IAAK,GAAW;AACxD,IAAA,EACE,GACA,yCAAyC,CAAA,oCAAyC,OAAO,CAAA,GAC3F;AACA;AAAA,EACF;AACA,SAAO,IAAI,KAAK,CAAE;AACpB,GAaa,IAAA,CAAe,MAAsE;AAEhG,MAAI,GAAK,YAAY,EAA0B,eAAgB;AAE/D,QAAM,IAAS,GAAK;AACpB,MAAI,CAAC,EAAQ;AAEb,QAAM,IAAS,EAAe,EAAO,MAAM;AAC3C,MAAI,MAAW,QAAW;AAGxB,IAAI,OAAO,KAAK,CAAM,EAAE,SAAS,KAC/B,EACE,GACA,mFACF;AAEF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAA;AAAA,IACA,WAAW,EAAO,EAAO,YAAY,cAAc,CAAM;AAAA,IACzD,SAAS,EAAO,EAAO,UAAU,YAAY,CAAM;AAAA,IACnD,OAAO,EAAQ,EAAO,KAAK;AAAA,EAC7B;AACF"}
|
|
@@ -1,147 +1,148 @@
|
|
|
1
|
-
import { useCategoryStore as
|
|
1
|
+
import { useCategoryStore as H, useGlobalCopyStore as P, useGlobalStore as V } from "../../../../common/context/hooks.es.js";
|
|
2
2
|
import { slot as s } from "../../../../core/types/Insights.es.js";
|
|
3
|
-
import { formatCurrency as
|
|
4
|
-
import { getCategoryColor as
|
|
5
|
-
import { useBeatActions as
|
|
6
|
-
import { USER_RESPONSE as
|
|
7
|
-
import { logoFromRef as
|
|
8
|
-
import
|
|
9
|
-
import { statusIconName as
|
|
10
|
-
import { resolveVariantConfig as
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import { subscriptionFrequency as
|
|
14
|
-
import { getLayoutConfig as
|
|
15
|
-
import { observer as
|
|
16
|
-
import { CategoryIcon as
|
|
17
|
-
import { Icon as
|
|
18
|
-
import { jsx as
|
|
19
|
-
import { alpha as
|
|
3
|
+
import { formatCurrency as C } from "../../../../core/utils/NumberFormatting.es.js";
|
|
4
|
+
import { getCategoryColor as I } from "../../../../core/utils/CategoryUtil.es.js";
|
|
5
|
+
import { useBeatActions as U } from "../../../beatActions.es.js";
|
|
6
|
+
import { USER_RESPONSE as W, repeatingTransactionOf as Y } from "../../../ctaActions/subscriptionAnswer.es.js";
|
|
7
|
+
import { logoFromRef as J } from "../../../components/shared/EntityLogo.es.js";
|
|
8
|
+
import K from "../../../components/LogoFromGuid.es.js";
|
|
9
|
+
import { statusIconName as Q, statusIconPalette as S } from "../../../components/statusIcon.es.js";
|
|
10
|
+
import { resolveVariantConfig as X } from "../../resolveVariantConfig.es.js";
|
|
11
|
+
import oo from "../../../components/TransactionListCard.es.js";
|
|
12
|
+
import to from "../../../components/StatusMessageCard.es.js";
|
|
13
|
+
import { subscriptionFrequency as ro } from "../../../components/SubscriptionStatus/subscriptionFrequency.es.js";
|
|
14
|
+
import { getLayoutConfig as eo, transactionBlockVariant as io } from "./layout.es.js";
|
|
15
|
+
import { observer as no } from "mobx-react-lite";
|
|
16
|
+
import { CategoryIcon as ao, InstitutionLogo as so, MerchantLogo as co } from "@mxenabled/mxui";
|
|
17
|
+
import { Icon as E } from "@mxenabled/mx-icons";
|
|
18
|
+
import { jsx as r } from "react/jsx-runtime";
|
|
19
|
+
import { alpha as G, useTheme as uo } from "@mui/material/styles";
|
|
20
20
|
import d from "@mui/material/Box";
|
|
21
|
-
var u = 32,
|
|
22
|
-
const
|
|
23
|
-
return
|
|
21
|
+
var u = 32, mo = Math.round(u * 0.625), lo = 24, T = 32, go = 20, fo = ({ icon: a }) => {
|
|
22
|
+
const n = Q(a?.guid);
|
|
23
|
+
return n ? /* @__PURE__ */ r(d, {
|
|
24
24
|
"aria-hidden": !0,
|
|
25
|
-
"data-icon":
|
|
25
|
+
"data-icon": n,
|
|
26
26
|
"data-testid": "subscription-badge",
|
|
27
27
|
sx: {
|
|
28
28
|
alignItems: "center",
|
|
29
|
-
bgcolor: `${
|
|
29
|
+
bgcolor: `${S(a?.color)}.main`,
|
|
30
30
|
borderRadius: "50%",
|
|
31
31
|
display: "flex",
|
|
32
|
-
height:
|
|
32
|
+
height: T,
|
|
33
33
|
justifyContent: "center",
|
|
34
|
-
width:
|
|
34
|
+
width: T
|
|
35
35
|
},
|
|
36
|
-
children: /* @__PURE__ */
|
|
36
|
+
children: /* @__PURE__ */ r(E, {
|
|
37
37
|
fill: !0,
|
|
38
|
-
name:
|
|
39
|
-
size:
|
|
40
|
-
sx: { color: `${
|
|
38
|
+
name: n,
|
|
39
|
+
size: go,
|
|
40
|
+
sx: { color: `${S(a?.color)}.contrastText` }
|
|
41
41
|
})
|
|
42
42
|
}) : null;
|
|
43
|
-
},
|
|
44
|
-
const o =
|
|
43
|
+
}, po = (a, n, c, m) => {
|
|
44
|
+
const o = J(a.logo, a.primary_text);
|
|
45
45
|
if (o) {
|
|
46
46
|
if (o.logo_name) {
|
|
47
|
-
const
|
|
48
|
-
return /* @__PURE__ */
|
|
47
|
+
const i = !!o.logo_fill, e = o.logo_color ?? c.palette.chart.chart2 ?? c.palette.primary.main;
|
|
48
|
+
return /* @__PURE__ */ r(d, {
|
|
49
49
|
sx: {
|
|
50
50
|
alignItems: "center",
|
|
51
51
|
display: "flex",
|
|
52
52
|
height: u,
|
|
53
53
|
justifyContent: "center",
|
|
54
54
|
width: u,
|
|
55
|
-
...
|
|
56
|
-
bgcolor:
|
|
55
|
+
...i && {
|
|
56
|
+
bgcolor: G(e, 0.15),
|
|
57
57
|
borderRadius: "8px"
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
|
-
children: /* @__PURE__ */
|
|
60
|
+
children: /* @__PURE__ */ r(E, {
|
|
61
61
|
name: o.logo_name,
|
|
62
|
-
size:
|
|
63
|
-
sx: { color:
|
|
62
|
+
size: mo,
|
|
63
|
+
sx: { color: i ? e : o.logo_color }
|
|
64
64
|
})
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
-
if (o.merchant_guid) return /* @__PURE__ */
|
|
67
|
+
if (o.merchant_guid) return /* @__PURE__ */ r(co, {
|
|
68
68
|
alt: o.merchant_name ?? "",
|
|
69
|
-
categoryGuid: o.category_guid ?
|
|
69
|
+
categoryGuid: o.category_guid ? n(o.category_guid) : "",
|
|
70
70
|
merchantGuid: o.merchant_guid,
|
|
71
71
|
size: u
|
|
72
72
|
});
|
|
73
|
-
if (o.institution_guid) return /* @__PURE__ */
|
|
73
|
+
if (o.institution_guid) return /* @__PURE__ */ r(so, {
|
|
74
74
|
alt: o.institution_name ?? "",
|
|
75
75
|
institutionGuid: o.institution_guid,
|
|
76
76
|
size: u
|
|
77
77
|
});
|
|
78
78
|
if (o.category_guid) {
|
|
79
|
-
const
|
|
80
|
-
bgcolor:
|
|
81
|
-
border: `1px solid ${
|
|
79
|
+
const i = n(o.category_guid), e = m ? {
|
|
80
|
+
bgcolor: G(I(i, c), 0.15),
|
|
81
|
+
border: `1px solid ${I(i, c)}`,
|
|
82
82
|
color: "text.primary"
|
|
83
83
|
} : void 0;
|
|
84
|
-
return /* @__PURE__ */
|
|
85
|
-
"data-testid": `category-icon-${
|
|
84
|
+
return /* @__PURE__ */ r(d, {
|
|
85
|
+
"data-testid": `category-icon-${i}`,
|
|
86
86
|
sx: { display: "flex" },
|
|
87
|
-
children: /* @__PURE__ */
|
|
88
|
-
categoryGuid:
|
|
87
|
+
children: /* @__PURE__ */ r(ao, {
|
|
88
|
+
categoryGuid: i,
|
|
89
89
|
size: u,
|
|
90
|
-
sx:
|
|
90
|
+
sx: e
|
|
91
91
|
})
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
},
|
|
96
|
-
const
|
|
97
|
-
beat:
|
|
98
|
-
stores:
|
|
99
|
-
}) : void 0, h = !!g && _?.user_response ===
|
|
100
|
-
if (
|
|
101
|
-
const
|
|
102
|
-
return typeof
|
|
103
|
-
icon: /* @__PURE__ */
|
|
95
|
+
}, Fo = no(({ payload: a }) => {
|
|
96
|
+
const n = uo(), { getTopLevelCategoryGuid: c } = H(), m = V(), { recurring: o } = P(), { beat: i } = U(), { data: e, variant: B } = a.instance_slot, { answeredBackground: p, background: l, cardVariant: L, creditAmounts: k, elevated: O, followsSubscription: g, headerColor: R, raised: A, showFooter: $, showFrequency: y, showHeader: z, statusMessage: F, sx: N, tintedCategoryLogo: M } = X(eo(n), B, io.NoHeader, "TransactionBlock"), _ = (y || g) && i ? Y({
|
|
97
|
+
beat: i,
|
|
98
|
+
stores: m
|
|
99
|
+
}) : void 0, h = !!g && _?.user_response === W.CONFIRMED;
|
|
100
|
+
if (F) {
|
|
101
|
+
const t = s(e, "message");
|
|
102
|
+
return typeof t != "string" || !t.trim() ? (console.warn("[TransactionBlock] status_message has no message — rendering nothing."), null) : /* @__PURE__ */ r(to, {
|
|
103
|
+
icon: /* @__PURE__ */ r(d, {
|
|
104
104
|
"aria-hidden": "true",
|
|
105
105
|
sx: { display: "flex" },
|
|
106
|
-
children: /* @__PURE__ */
|
|
107
|
-
logo: s(
|
|
108
|
-
size:
|
|
106
|
+
children: /* @__PURE__ */ r(K, {
|
|
107
|
+
logo: s(e, "icon"),
|
|
108
|
+
size: lo
|
|
109
109
|
})
|
|
110
110
|
}),
|
|
111
|
-
message:
|
|
112
|
-
sx:
|
|
113
|
-
backgroundImage:
|
|
111
|
+
message: t,
|
|
112
|
+
sx: l ? {
|
|
113
|
+
backgroundImage: l,
|
|
114
114
|
bgcolor: "background.paper"
|
|
115
115
|
} : void 0
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
|
-
const f = s(
|
|
118
|
+
const f = s(e, "series"), x = s(e, "header_text");
|
|
119
119
|
if (!Array.isArray(f) || !f.length) return null;
|
|
120
|
-
const
|
|
120
|
+
const Z = z && typeof x == "string" ? x : void 0, b = s(e, "footer_text"), w = s(e, "footer_amount"), j = $ && typeof b == "string" ? {
|
|
121
121
|
label: b,
|
|
122
|
-
amount:
|
|
123
|
-
} : void 0,
|
|
124
|
-
return /* @__PURE__ */
|
|
125
|
-
background: h && p ? p :
|
|
126
|
-
badge:
|
|
127
|
-
elevated:
|
|
128
|
-
footer:
|
|
129
|
-
header:
|
|
130
|
-
headerColor:
|
|
131
|
-
raised:
|
|
132
|
-
sx:
|
|
133
|
-
variant:
|
|
134
|
-
rows: f.map((
|
|
135
|
-
amount: typeof
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
122
|
+
amount: C(w, "0,0.00")
|
|
123
|
+
} : void 0, q = y ? ro(o, _) : void 0, D = g ? /* @__PURE__ */ r(fo, { icon: s(e, h ? "reviewed_icon" : "icon") }) : void 0, v = (t) => !!k && typeof t == "number" && t > 0;
|
|
124
|
+
return /* @__PURE__ */ r(oo, {
|
|
125
|
+
background: h && p ? p : l,
|
|
126
|
+
badge: D,
|
|
127
|
+
elevated: O,
|
|
128
|
+
footer: j,
|
|
129
|
+
header: Z,
|
|
130
|
+
headerColor: R,
|
|
131
|
+
raised: A,
|
|
132
|
+
sx: N,
|
|
133
|
+
variant: L,
|
|
134
|
+
rows: f.map((t) => ({
|
|
135
|
+
amount: typeof t.amount == "number" ? `${v(t.amount) ? "+" : ""}${C(t.amount, "0,0.00")}` : "",
|
|
136
|
+
amountColor: v(t.amount) ? "success.main" : void 0,
|
|
137
|
+
amountCaption: q,
|
|
138
|
+
leading: po(t, c, n, M ?? !1),
|
|
139
|
+
subtitle: t.secondary_text ?? "",
|
|
140
|
+
title: t.primary_text ?? ""
|
|
140
141
|
}))
|
|
141
142
|
});
|
|
142
143
|
});
|
|
143
144
|
export {
|
|
144
|
-
|
|
145
|
+
Fo as TransactionBlock
|
|
145
146
|
};
|
|
146
147
|
|
|
147
148
|
//# sourceMappingURL=TransactionBlock.es.js.map
|