@mx-cartographer/experiences 9.4.62 → 9.4.63
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 +4 -0
- package/dist/core/stores/TrendsStore.d.ts +14 -0
- package/dist/core/stores/TrendsStore.es.js +43 -30
- package/dist/core/stores/TrendsStore.es.js.map +1 -1
- package/dist/trends/TrendsWidget.es.js +127 -122
- package/dist/trends/TrendsWidget.es.js.map +1 -1
- package/dist/trends/components/TrendsTableContainer.es.js +0 -2
- package/dist/trends/components/TrendsTableContainer.es.js.map +1 -1
- package/dist/trends/components/trendstable/TrendsTable.d.ts +1 -3
- package/dist/trends/components/trendstable/TrendsTable.es.js +29 -22
- package/dist/trends/components/trendstable/TrendsTable.es.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## [9.4.63] - 10-07-2026
|
|
2
|
+
|
|
3
|
+
- **FIXED** - Trends Tabular View child category rows now open the filtered transaction drawer, matching the chart view MEW-4020
|
|
4
|
+
|
|
1
5
|
## [9.4.62] - 10-07-2026
|
|
2
6
|
|
|
3
7
|
- **ADDED** - `withdrawal_block` (Highlight Withdrawal) component block and `WithdrawalCard`: the amount (shown with a leading minus unless it is zero), an `"{amount_label} {withdrawal_date}"` date line and a grey balance footer, read from one `instance_slot.data_series` row (`amount`, `amount_label`, `withdrawal_date`, `balance`, `balance_label`). Text arrives localized from the backend; only `amount` and `balance` are formatted. (WEC-1871)
|
|
@@ -7,6 +7,20 @@ export declare class TrendsStore {
|
|
|
7
7
|
visibleListLength: number;
|
|
8
8
|
constructor(globalStore: GlobalStore);
|
|
9
9
|
setSelectedCategoryData: (data: DetailedCategoryWithTransactions | null) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a category guid — top level or sub-category — to the data the
|
|
12
|
+
* category details drawer renders.
|
|
13
|
+
*
|
|
14
|
+
* `detailedCategoriesWithTransactions` only holds top-level categories, and a
|
|
15
|
+
* sub-category carries `totalAmount: 0` / `totalMonthlyAmounts: []` so it is
|
|
16
|
+
* not double counted inside its parent. The drawer reads both fields, so a
|
|
17
|
+
* sub-category is rebuilt from its own range total and monthly series.
|
|
18
|
+
*
|
|
19
|
+
* The tabular view only routes sub-category guids here today; the top-level
|
|
20
|
+
* branch keeps this a complete resolver for any guid the drawer is given.
|
|
21
|
+
* @param guid - Guid of the category to resolve
|
|
22
|
+
*/
|
|
23
|
+
getCategoryWithTransactions: (guid: string) => DetailedCategoryWithTransactions | undefined;
|
|
10
24
|
setSelectedDateRange: (dateRange: DateRange) => void;
|
|
11
25
|
setVisibleListLength: (length: number) => void;
|
|
12
26
|
get sortedDetailedCategoriesWithTransactions(): DetailedCategoryWithTransactions[];
|
|
@@ -1,47 +1,60 @@
|
|
|
1
|
-
import { DATE_FORMATS_INTL as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
1
|
+
import { DATE_FORMATS_INTL as o, formatDate as r } from "../constants/DateFormats.es.js";
|
|
2
|
+
import { findDetailedSubCategory as d, getDetailedCategoryTransactions as c } from "../utils/CategoryUtil.es.js";
|
|
3
|
+
import { buildCategoryDetailsChartData as h, filterAndSortCategoriesWithTransactions as D } from "../utils/TrendsUtil.es.js";
|
|
4
|
+
import { endOfMonth as i } from "date-fns/endOfMonth";
|
|
5
|
+
import { isBefore as u } from "date-fns/isBefore";
|
|
6
|
+
import { subMonths as m } from "date-fns/subMonths";
|
|
7
|
+
import { differenceInCalendarMonths as C } from "date-fns/differenceInCalendarMonths";
|
|
8
|
+
import { makeAutoObservable as S } from "mobx";
|
|
9
|
+
import { addSeconds as b } from "date-fns/addSeconds";
|
|
10
|
+
var W = class {
|
|
10
11
|
globalStore;
|
|
11
12
|
selectedCategoryData = null;
|
|
12
13
|
selectedDateRange;
|
|
13
14
|
visibleListLength = 5;
|
|
14
|
-
constructor(
|
|
15
|
-
this.globalStore =
|
|
16
|
-
start:
|
|
17
|
-
end:
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
setSelectedCategoryData = (
|
|
21
|
-
this.selectedCategoryData =
|
|
15
|
+
constructor(t) {
|
|
16
|
+
this.globalStore = t, this.selectedDateRange = {
|
|
17
|
+
start: b(i(m(/* @__PURE__ */ new Date(), 6)), 1),
|
|
18
|
+
end: i(/* @__PURE__ */ new Date())
|
|
19
|
+
}, S(this);
|
|
20
|
+
}
|
|
21
|
+
setSelectedCategoryData = (t) => {
|
|
22
|
+
this.selectedCategoryData = t;
|
|
23
|
+
};
|
|
24
|
+
getCategoryWithTransactions = (t) => {
|
|
25
|
+
const { detailedCategories: e, detailedCategoriesWithTransactions: n } = this.globalStore.categoryStore, s = n.find((g) => g.guid === t);
|
|
26
|
+
if (s) return s;
|
|
27
|
+
const a = d(e, t);
|
|
28
|
+
if (!a) return;
|
|
29
|
+
const [l] = c([{
|
|
30
|
+
...a,
|
|
31
|
+
totalAmount: a.currentAmount,
|
|
32
|
+
totalMonthlyAmounts: a.monthlyAmounts
|
|
33
|
+
}], this.globalStore.transactionStore.sortedTransactions);
|
|
34
|
+
return l;
|
|
22
35
|
};
|
|
23
|
-
setSelectedDateRange = (
|
|
24
|
-
this.selectedDateRange =
|
|
36
|
+
setSelectedDateRange = (t) => {
|
|
37
|
+
this.selectedDateRange = t;
|
|
25
38
|
};
|
|
26
|
-
setVisibleListLength = (
|
|
27
|
-
this.visibleListLength =
|
|
39
|
+
setVisibleListLength = (t) => {
|
|
40
|
+
this.visibleListLength = t;
|
|
28
41
|
};
|
|
29
42
|
get sortedDetailedCategoriesWithTransactions() {
|
|
30
|
-
return
|
|
43
|
+
return D(this.globalStore.categoryStore.detailedCategoriesWithTransactions);
|
|
31
44
|
}
|
|
32
45
|
get isTransactionStoreIdle() {
|
|
33
|
-
const { isTransactionDataLoaded:
|
|
34
|
-
return
|
|
46
|
+
const { isTransactionDataLoaded: t, transactionsLoading: e } = this.globalStore.transactionStore;
|
|
47
|
+
return t && !e;
|
|
35
48
|
}
|
|
36
49
|
get isSelectedRangeLoaded() {
|
|
37
|
-
const { isTransactionDataLoaded:
|
|
38
|
-
return
|
|
50
|
+
const { isTransactionDataLoaded: t, cachedStartDate: e } = this.globalStore.transactionStore;
|
|
51
|
+
return t && !u(this.selectedDateRange.start, e);
|
|
39
52
|
}
|
|
40
53
|
get selectedDateRangeMonthCount() {
|
|
41
|
-
return
|
|
54
|
+
return C(this.selectedDateRange.end, this.selectedDateRange.start) + 1;
|
|
42
55
|
}
|
|
43
56
|
get selectedDateRangeMonthRange() {
|
|
44
|
-
return `${
|
|
57
|
+
return `${r(this.selectedDateRange.start, o.MONTH_LONG)} - ${r(this.selectedDateRange.end, o.MONTH_LONG)}`;
|
|
45
58
|
}
|
|
46
59
|
get visibleCategories() {
|
|
47
60
|
return this.sortedDetailedCategoriesWithTransactions.slice(0, this.visibleListLength);
|
|
@@ -50,11 +63,11 @@ var R = class {
|
|
|
50
63
|
return this.sortedDetailedCategoriesWithTransactions.slice(this.visibleListLength);
|
|
51
64
|
}
|
|
52
65
|
get categoryDetailsChartData() {
|
|
53
|
-
return
|
|
66
|
+
return h(this.selectedCategoryData, this.selectedDateRangeMonthCount);
|
|
54
67
|
}
|
|
55
68
|
};
|
|
56
69
|
export {
|
|
57
|
-
|
|
70
|
+
W as TrendsStore
|
|
58
71
|
};
|
|
59
72
|
|
|
60
73
|
//# sourceMappingURL=TrendsStore.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrendsStore.es.js","names":[],"sources":["../../../src/core/stores/TrendsStore.ts"],"sourcesContent":["import { makeAutoObservable } from 'mobx'\nimport { addSeconds } from 'date-fns/addSeconds'\nimport { differenceInCalendarMonths } from 'date-fns/differenceInCalendarMonths'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { isBefore } from 'date-fns/isBefore'\nimport { subMonths } from 'date-fns/subMonths'\n\nimport type { DateRange, DetailedCategoryWithTransactions } from '../types'\nimport { DATE_FORMATS_INTL, formatDate } from '../constants'\nimport {\n buildCategoryDetailsChartData,\n filterAndSortCategoriesWithTransactions,\n} from '../utils/TrendsUtil'\n\nimport { GlobalStore } from './GlobalStore'\n\nexport class TrendsStore {\n globalStore: GlobalStore\n selectedCategoryData: DetailedCategoryWithTransactions | null = null\n selectedDateRange: DateRange\n visibleListLength: number = 5\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n this.selectedDateRange = {\n start: addSeconds(endOfMonth(subMonths(new Date(), 6)), 1),\n end: endOfMonth(new Date()),\n }\n makeAutoObservable(this)\n }\n\n setSelectedCategoryData = (data: DetailedCategoryWithTransactions | null) => {\n this.selectedCategoryData = data\n }\n\n setSelectedDateRange = (dateRange: DateRange) => {\n this.selectedDateRange = dateRange\n }\n\n setVisibleListLength = (length: number) => {\n this.visibleListLength = length\n }\n\n get sortedDetailedCategoriesWithTransactions(): DetailedCategoryWithTransactions[] {\n return filterAndSortCategoriesWithTransactions(\n this.globalStore.categoryStore.detailedCategoriesWithTransactions,\n )\n }\n\n get isTransactionStoreIdle(): boolean {\n const { isTransactionDataLoaded, transactionsLoading } = this.globalStore.transactionStore\n return isTransactionDataLoaded && !transactionsLoading\n }\n\n get isSelectedRangeLoaded(): boolean {\n const { isTransactionDataLoaded, cachedStartDate } = this.globalStore.transactionStore\n return isTransactionDataLoaded && !isBefore(this.selectedDateRange.start, cachedStartDate)\n }\n\n get selectedDateRangeMonthCount(): number {\n return differenceInCalendarMonths(this.selectedDateRange.end, this.selectedDateRange.start) + 1\n }\n\n get selectedDateRangeMonthRange(): string {\n const startMonth = formatDate(this.selectedDateRange.start, DATE_FORMATS_INTL.MONTH_LONG)\n const endMonth = formatDate(this.selectedDateRange.end, DATE_FORMATS_INTL.MONTH_LONG)\n return `${startMonth} - ${endMonth}`\n }\n\n get visibleCategories(): DetailedCategoryWithTransactions[] {\n return this.sortedDetailedCategoriesWithTransactions.slice(0, this.visibleListLength)\n }\n\n get collapsedCategories(): DetailedCategoryWithTransactions[] {\n return this.sortedDetailedCategoriesWithTransactions.slice(this.visibleListLength)\n }\n\n get categoryDetailsChartData() {\n return buildCategoryDetailsChartData(\n this.selectedCategoryData,\n this.selectedDateRangeMonthCount,\n )\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"TrendsStore.es.js","names":[],"sources":["../../../src/core/stores/TrendsStore.ts"],"sourcesContent":["import { makeAutoObservable } from 'mobx'\nimport { addSeconds } from 'date-fns/addSeconds'\nimport { differenceInCalendarMonths } from 'date-fns/differenceInCalendarMonths'\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { isBefore } from 'date-fns/isBefore'\nimport { subMonths } from 'date-fns/subMonths'\n\nimport type { DateRange, DetailedCategoryWithTransactions } from '../types'\nimport { DATE_FORMATS_INTL, formatDate } from '../constants'\nimport {\n buildCategoryDetailsChartData,\n filterAndSortCategoriesWithTransactions,\n} from '../utils/TrendsUtil'\nimport { findDetailedSubCategory, getDetailedCategoryTransactions } from '../utils/CategoryUtil'\n\nimport { GlobalStore } from './GlobalStore'\n\nexport class TrendsStore {\n globalStore: GlobalStore\n selectedCategoryData: DetailedCategoryWithTransactions | null = null\n selectedDateRange: DateRange\n visibleListLength: number = 5\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n this.selectedDateRange = {\n start: addSeconds(endOfMonth(subMonths(new Date(), 6)), 1),\n end: endOfMonth(new Date()),\n }\n makeAutoObservable(this)\n }\n\n setSelectedCategoryData = (data: DetailedCategoryWithTransactions | null) => {\n this.selectedCategoryData = data\n }\n\n /**\n * Resolve a category guid — top level or sub-category — to the data the\n * category details drawer renders.\n *\n * `detailedCategoriesWithTransactions` only holds top-level categories, and a\n * sub-category carries `totalAmount: 0` / `totalMonthlyAmounts: []` so it is\n * not double counted inside its parent. The drawer reads both fields, so a\n * sub-category is rebuilt from its own range total and monthly series.\n *\n * The tabular view only routes sub-category guids here today; the top-level\n * branch keeps this a complete resolver for any guid the drawer is given.\n * @param guid - Guid of the category to resolve\n */\n getCategoryWithTransactions = (guid: string): DetailedCategoryWithTransactions | undefined => {\n const { detailedCategories, detailedCategoriesWithTransactions } =\n this.globalStore.categoryStore\n\n const topLevelCategory = detailedCategoriesWithTransactions.find(\n (category) => category.guid === guid,\n )\n if (topLevelCategory) return topLevelCategory\n\n const subCategory = findDetailedSubCategory(detailedCategories, guid)\n if (!subCategory) return undefined\n\n const [subCategoryWithTransactions] = getDetailedCategoryTransactions(\n [\n {\n ...subCategory,\n totalAmount: subCategory.currentAmount,\n totalMonthlyAmounts: subCategory.monthlyAmounts,\n },\n ],\n this.globalStore.transactionStore.sortedTransactions,\n )\n\n return subCategoryWithTransactions\n }\n\n setSelectedDateRange = (dateRange: DateRange) => {\n this.selectedDateRange = dateRange\n }\n\n setVisibleListLength = (length: number) => {\n this.visibleListLength = length\n }\n\n get sortedDetailedCategoriesWithTransactions(): DetailedCategoryWithTransactions[] {\n return filterAndSortCategoriesWithTransactions(\n this.globalStore.categoryStore.detailedCategoriesWithTransactions,\n )\n }\n\n get isTransactionStoreIdle(): boolean {\n const { isTransactionDataLoaded, transactionsLoading } = this.globalStore.transactionStore\n return isTransactionDataLoaded && !transactionsLoading\n }\n\n get isSelectedRangeLoaded(): boolean {\n const { isTransactionDataLoaded, cachedStartDate } = this.globalStore.transactionStore\n return isTransactionDataLoaded && !isBefore(this.selectedDateRange.start, cachedStartDate)\n }\n\n get selectedDateRangeMonthCount(): number {\n return differenceInCalendarMonths(this.selectedDateRange.end, this.selectedDateRange.start) + 1\n }\n\n get selectedDateRangeMonthRange(): string {\n const startMonth = formatDate(this.selectedDateRange.start, DATE_FORMATS_INTL.MONTH_LONG)\n const endMonth = formatDate(this.selectedDateRange.end, DATE_FORMATS_INTL.MONTH_LONG)\n return `${startMonth} - ${endMonth}`\n }\n\n get visibleCategories(): DetailedCategoryWithTransactions[] {\n return this.sortedDetailedCategoriesWithTransactions.slice(0, this.visibleListLength)\n }\n\n get collapsedCategories(): DetailedCategoryWithTransactions[] {\n return this.sortedDetailedCategoriesWithTransactions.slice(this.visibleListLength)\n }\n\n get categoryDetailsChartData() {\n return buildCategoryDetailsChartData(\n this.selectedCategoryData,\n this.selectedDateRangeMonthCount,\n )\n }\n}\n"],"mappings":";;;;;;;;;AAiBA,IAAa,IAAb,MAAyB;AAAA,EACvB;AAAA,EACA,uBAAgE;AAAA,EAChE;AAAA,EACA,oBAA4B;AAAA,EAE5B,YAAY,GAA0B;AACpC,SAAK,cAAc,GACnB,KAAK,oBAAoB;AAAA,MACvB,OAAO,EAAW,EAAW,EAAU,oBAAI,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC;AAAA,MACzD,KAAK,EAAW,oBAAI,KAAK,CAAC;AAAA,IAC5B,GACA,EAAmB,IAAI;AAAA,EACzB;AAAA,EAEA,0BAAA,CAA2B,MAAkD;AAC3E,SAAK,uBAAuB;AAAA,EAC9B;AAAA,EAeA,8BAAA,CAA+B,MAA+D;AAC5F,UAAM,EAAE,oBAAA,GAAoB,oCAAA,EAAA,IAC1B,KAAK,YAAY,eAEb,IAAmB,EAAmC,KAAA,CACzD,MAAa,EAAS,SAAS,CAClC;AACA,QAAI,EAAkB,QAAO;AAE7B,UAAM,IAAc,EAAwB,GAAoB,CAAI;AACpE,QAAI,CAAC,EAAa;AAElB,UAAM,CAAC,CAAA,IAA+B,EACpC,CACE;AAAA,MACE,GAAG;AAAA,MACH,aAAa,EAAY;AAAA,MACzB,qBAAqB,EAAY;AAAA,IACnC,CACF,GACA,KAAK,YAAY,iBAAiB,kBACpC;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,uBAAA,CAAwB,MAAyB;AAC/C,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEA,uBAAA,CAAwB,MAAmB;AACzC,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEA,IAAI,2CAA+E;AACjF,WAAO,EACL,KAAK,YAAY,cAAc,kCACjC;AAAA,EACF;AAAA,EAEA,IAAI,yBAAkC;AACpC,UAAM,EAAE,yBAAA,GAAyB,qBAAA,EAAA,IAAwB,KAAK,YAAY;AAC1E,WAAO,KAA2B,CAAC;AAAA,EACrC;AAAA,EAEA,IAAI,wBAAiC;AACnC,UAAM,EAAE,yBAAA,GAAyB,iBAAA,EAAA,IAAoB,KAAK,YAAY;AACtE,WAAO,KAA2B,CAAC,EAAS,KAAK,kBAAkB,OAAO,CAAe;AAAA,EAC3F;AAAA,EAEA,IAAI,8BAAsC;AACxC,WAAO,EAA2B,KAAK,kBAAkB,KAAK,KAAK,kBAAkB,KAAK,IAAI;AAAA,EAChG;AAAA,EAEA,IAAI,8BAAsC;AAGxC,WAAO,GAFY,EAAW,KAAK,kBAAkB,OAAO,EAAkB,UAEpE,CAAA,MADO,EAAW,KAAK,kBAAkB,KAAK,EAAkB,UAChD,CAAA;AAAA,EAC5B;AAAA,EAEA,IAAI,oBAAwD;AAC1D,WAAO,KAAK,yCAAyC,MAAM,GAAG,KAAK,iBAAiB;AAAA,EACtF;AAAA,EAEA,IAAI,sBAA0D;AAC5D,WAAO,KAAK,yCAAyC,MAAM,KAAK,iBAAiB;AAAA,EACnF;AAAA,EAEA,IAAI,2BAA2B;AAC7B,WAAO,EACL,KAAK,sBACL,KAAK,2BACP;AAAA,EACF;AACF"}
|
|
@@ -1,137 +1,142 @@
|
|
|
1
|
-
import { useAccountStore as
|
|
2
|
-
import { useInsightsEnabled as
|
|
3
|
-
import { useScreenSize as
|
|
4
|
-
import { useWidgetLoadTimer as
|
|
5
|
-
import
|
|
1
|
+
import { useAccountStore as we, useAppConfig as De, useCategoryStore as Le, useEvent as be, useGlobalCopyStore as Ie, useGlobalStore as Ae, useGlobalUiStore as Re, useTransactionStore as xe, useTrendsStore as ve } from "../common/context/hooks.es.js";
|
|
2
|
+
import { useInsightsEnabled as Ne } from "../common/hooks/useInsightsEnabled.es.js";
|
|
3
|
+
import { useScreenSize as We } from "../common/hooks/useScreenSize.es.js";
|
|
4
|
+
import { useWidgetLoadTimer as Be } from "../common/hooks/useWidgetLoadTimer.es.js";
|
|
5
|
+
import ke from "../common/components/drawer/Drawer.es.js";
|
|
6
6
|
import { ANALYTICS_EVENTS as c } from "../common/constants/Analytics.es.js";
|
|
7
|
-
import
|
|
8
|
-
import { WidgetContainer as
|
|
9
|
-
import
|
|
10
|
-
import { EmptyState as
|
|
7
|
+
import He from "../common/components/Loader.es.js";
|
|
8
|
+
import { WidgetContainer as Ge } from "../common/components/WidgetContainer.es.js";
|
|
9
|
+
import Fe from "../common/components/ConnectDrawer.es.js";
|
|
10
|
+
import { EmptyState as Me } from "../common/components/EmptyState.es.js";
|
|
11
11
|
import { TRENDS_ANALYTICS_METADATA as m } from "./constants/TrendsWidget.es.js";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import { CategoryDetails as
|
|
18
|
-
import
|
|
19
|
-
import { TRENDS_BEAT_TEMPLATES as
|
|
20
|
-
import { buildEmptyStateProps as
|
|
21
|
-
import
|
|
22
|
-
import { observer as
|
|
12
|
+
import Oe from "./components/TrendsViewToggle.es.js";
|
|
13
|
+
import Ve from "./components/DateRangeText.es.js";
|
|
14
|
+
import ze from "./components/TrendsTableContainer.es.js";
|
|
15
|
+
import Ke from "./components/CategoriesList.es.js";
|
|
16
|
+
import je from "./components/TrendsInsights.es.js";
|
|
17
|
+
import { CategoryDetails as Pe } from "./components/CategoryDetails.es.js";
|
|
18
|
+
import $e from "./components/TrendsChartTotals.es.js";
|
|
19
|
+
import { TRENDS_BEAT_TEMPLATES as Ue } from "./constants/TrendsInsights.es.js";
|
|
20
|
+
import { buildEmptyStateProps as Ye } from "./utils/TrendsWidget.es.js";
|
|
21
|
+
import n from "react";
|
|
22
|
+
import { observer as qe } from "mobx-react-lite";
|
|
23
23
|
import f from "@mui/material/Stack";
|
|
24
|
-
import
|
|
25
|
-
import { ArrowBack as
|
|
26
|
-
import { Fragment as
|
|
24
|
+
import Je from "@mui/material/Button";
|
|
25
|
+
import { ArrowBack as Qe } from "@mxenabled/mx-icons";
|
|
26
|
+
import { Fragment as Xe, jsx as t, jsxs as l } from "react/jsx-runtime";
|
|
27
27
|
import w from "@mui/material/Box";
|
|
28
|
-
import { differenceInCalendarMonths as
|
|
29
|
-
import { addSeconds as
|
|
30
|
-
var
|
|
31
|
-
const { onEvent: s } =
|
|
32
|
-
if (
|
|
33
|
-
const
|
|
34
|
-
|
|
28
|
+
import { differenceInCalendarMonths as M } from "date-fns/differenceInCalendarMonths";
|
|
29
|
+
import { addSeconds as Ze } from "date-fns/addSeconds";
|
|
30
|
+
var et = ({ onBackClick: O, onMenuClick: V, onInsightCardClick: z, sx: K }) => {
|
|
31
|
+
const { onEvent: s } = be(), { config: D } = De(), { isDesktop: j, isLargeDesktop: P, isMobile: T } = We(), { isAccountDataLoaded: u, loadAccountData: $, visibleAccounts: U } = we(), { categoriesLoaded: Y, loadCategories: q, monthlyCategoryTotals: a, monthlyTotalsLoaded: J, reloadCategoryTotals: Q } = Le(), { isTransactionDataLoaded: X, loadTransactionData: Z, setFilter: ee } = xe(), { getCategoryWithTransactions: te, isTransactionStoreIdle: L, selectedCategoryData: _, selectedDateRange: r, setSelectedCategoryData: b, setSelectedDateRange: oe } = ve(), { isInitialized: S, selectedAccounts: ne, selectedAccountGuids: I } = Re(), { trends: d, connect: re } = Ie(), { beatStore: A, captureException: ie } = Ae(), R = Ne() && D.show_insights_widget_in_master, g = j || P, [x, se] = n.useState(!1), [y, ae] = n.useState(!1), [p, ce] = n.useState("Chart"), [h, v] = n.useState(""), [N, le] = n.useState(window.innerHeight), de = g ? 56 : 0, W = N - 208, me = N - (R ? 550 + de : 266), [ge, B] = n.useState(!1), fe = D.show_connections_widget_in_master, E = n.useRef(void 0), ue = n.useCallback((e) => {
|
|
32
|
+
if (E.current?.(), E.current = void 0, !e) return;
|
|
33
|
+
const o = (i) => {
|
|
34
|
+
i.target instanceof HTMLElement && i.target.matches(":focus-visible") && i.target.scrollIntoView({ block: "nearest" });
|
|
35
35
|
};
|
|
36
|
-
e.addEventListener("focusin",
|
|
36
|
+
e.addEventListener("focusin", o), E.current = () => e.removeEventListener("focusin", o);
|
|
37
37
|
}, []);
|
|
38
|
-
|
|
38
|
+
Be({
|
|
39
39
|
widgetName: "TrendsWidget",
|
|
40
|
-
isLoaded:
|
|
40
|
+
isLoaded: x
|
|
41
41
|
});
|
|
42
|
-
const k =
|
|
43
|
-
|
|
44
|
-
const e = () =>
|
|
45
|
-
return window.addEventListener("resize", e), u ||
|
|
46
|
-
}, []),
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
const k = n.useMemo(() => y ? A.getFilteredBeats({ templates: Ue }) : [], [y]);
|
|
43
|
+
n.useEffect(() => {
|
|
44
|
+
const e = () => le(window.innerHeight);
|
|
45
|
+
return window.addEventListener("resize", e), u || $().finally(), Y || q().finally(), X || Z().finally(), () => window.removeEventListener("resize", e);
|
|
46
|
+
}, []), n.useEffect(() => {
|
|
47
|
+
S && u && (A.loadBeats().finally(() => ae(!0)), Q(ne, r.start, r.end).finally(() => {
|
|
48
|
+
se(!0), s(c.TRENDS_LOAD_WIDGET, {
|
|
49
49
|
...m,
|
|
50
|
-
time_period: `${
|
|
50
|
+
time_period: `${M(r.end, r.start)}M`
|
|
51
51
|
});
|
|
52
52
|
}));
|
|
53
53
|
}, [
|
|
54
|
-
|
|
54
|
+
S,
|
|
55
55
|
u,
|
|
56
56
|
I.join(","),
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
r.start.getTime(),
|
|
58
|
+
r.end.getTime()
|
|
59
59
|
]);
|
|
60
|
-
const H =
|
|
61
|
-
|
|
60
|
+
const H = n.useRef(null);
|
|
61
|
+
n.useEffect(() => {
|
|
62
62
|
if (a.length !== 0 && L && H.current !== a) {
|
|
63
63
|
H.current = a;
|
|
64
64
|
const e = {
|
|
65
65
|
accounts: I,
|
|
66
66
|
dateRange: {
|
|
67
|
-
start:
|
|
68
|
-
end:
|
|
67
|
+
start: r.start,
|
|
68
|
+
end: r.end
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
-
|
|
71
|
+
ee({
|
|
72
72
|
...e,
|
|
73
|
-
custom: (
|
|
73
|
+
custom: (o) => !!a.find((i) => i.top_level_category_guid === o.top_level_category_guid || i.category_guid === o.category_guid)
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
}, [a, L]);
|
|
77
|
-
const
|
|
77
|
+
const G = (e) => {
|
|
78
78
|
b(e);
|
|
79
|
-
},
|
|
79
|
+
}, pe = () => {
|
|
80
80
|
b(null), s(c.TRENDS_CLICK_BACK, m);
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
|
|
81
|
+
}, he = () => {
|
|
82
|
+
B(!0), s(c.TRENDS_CLICK_CONNECT_ACCOUNTS);
|
|
83
|
+
}, Ce = (e, o) => {
|
|
84
|
+
ce(o ?? p), s(c.TRENDS_CLICK_TOGGLE_VIEW, {
|
|
85
85
|
...m,
|
|
86
|
-
toggleView:
|
|
86
|
+
toggleView: o
|
|
87
87
|
});
|
|
88
|
-
},
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
start:
|
|
92
|
-
end:
|
|
88
|
+
}, Te = (e) => {
|
|
89
|
+
const o = Ze(e?.[0], 1), i = e?.[1], Ee = M(i, o);
|
|
90
|
+
oe({
|
|
91
|
+
start: o,
|
|
92
|
+
end: i
|
|
93
93
|
}), s(c.TRENDS_CLICK_TIME_WINDOW, {
|
|
94
94
|
...m,
|
|
95
|
-
time_period:
|
|
95
|
+
time_period: Ee + "M"
|
|
96
96
|
});
|
|
97
|
-
},
|
|
97
|
+
}, _e = (e) => {
|
|
98
98
|
s(c.TRENDS_CLICK_FILTER, {
|
|
99
99
|
...m,
|
|
100
100
|
filterValue: e
|
|
101
101
|
});
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
}, Se = (e) => {
|
|
103
|
+
if (!h) {
|
|
104
|
+
v(e);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const o = te(e);
|
|
108
|
+
o ? G(o) : ie(/* @__PURE__ */ new Error(`TrendsWidget: no category found for guid ${e}`));
|
|
109
|
+
}, ye = () => {
|
|
110
|
+
v(""), s(c.TRENDS_CLICK_ALL_CATEGORIES, m);
|
|
106
111
|
};
|
|
107
|
-
if (!
|
|
108
|
-
const
|
|
109
|
-
return /* @__PURE__ */ l(
|
|
110
|
-
calendarActions: { onRangeChanged:
|
|
111
|
-
dateRange:
|
|
112
|
-
dateRangeVariant:
|
|
113
|
-
onAccountsFilterClick:
|
|
114
|
-
onBackClick:
|
|
115
|
-
onMenuClick:
|
|
116
|
-
sx:
|
|
112
|
+
if (!S || !u || !x || !J) return /* @__PURE__ */ t(He, {});
|
|
113
|
+
const F = U.length === 0 || a.length === 0, C = Ye(d, fe, he);
|
|
114
|
+
return /* @__PURE__ */ l(Ge, {
|
|
115
|
+
calendarActions: { onRangeChanged: Te },
|
|
116
|
+
dateRange: r,
|
|
117
|
+
dateRangeVariant: T ? "timeframetabs" : "timeframebuttons",
|
|
118
|
+
onAccountsFilterClick: _e,
|
|
119
|
+
onBackClick: O,
|
|
120
|
+
onMenuClick: V,
|
|
121
|
+
sx: K,
|
|
117
122
|
title: d.title,
|
|
118
123
|
children: [/* @__PURE__ */ l(f, {
|
|
119
|
-
ref:
|
|
120
|
-
sx: { px:
|
|
124
|
+
ref: ue,
|
|
125
|
+
sx: { px: T ? 0 : 48 },
|
|
121
126
|
children: [
|
|
122
127
|
/* @__PURE__ */ l(f, {
|
|
123
128
|
flexDirection: "row",
|
|
124
129
|
justifyContent: "space-between",
|
|
125
130
|
sx: {
|
|
126
|
-
px:
|
|
131
|
+
px: T ? 16 : 0,
|
|
127
132
|
py: 16
|
|
128
133
|
},
|
|
129
|
-
children: [/* @__PURE__ */ t(
|
|
130
|
-
selectedCategoryGuid:
|
|
131
|
-
selectedDateRange:
|
|
132
|
-
}), /* @__PURE__ */ t(
|
|
133
|
-
onTabChange:
|
|
134
|
-
selectedTab:
|
|
134
|
+
children: [/* @__PURE__ */ t(Ve, {
|
|
135
|
+
selectedCategoryGuid: h,
|
|
136
|
+
selectedDateRange: r
|
|
137
|
+
}), /* @__PURE__ */ t(Oe, {
|
|
138
|
+
onTabChange: Ce,
|
|
139
|
+
selectedTab: p
|
|
135
140
|
})]
|
|
136
141
|
}),
|
|
137
142
|
/* @__PURE__ */ t(f, {
|
|
@@ -141,76 +146,76 @@ var Xe = ({ onBackClick: M, onMenuClick: O, onInsightCardClick: V, sx: z }) => {
|
|
|
141
146
|
width: "100%",
|
|
142
147
|
children: /* @__PURE__ */ t(w, {
|
|
143
148
|
flexGrow: 1,
|
|
144
|
-
children:
|
|
145
|
-
onClick:
|
|
149
|
+
children: h && /* @__PURE__ */ l(Je, {
|
|
150
|
+
onClick: ye,
|
|
146
151
|
sx: {
|
|
147
152
|
p: 0,
|
|
148
153
|
pr: 5
|
|
149
154
|
},
|
|
150
|
-
children: [/* @__PURE__ */ t(
|
|
155
|
+
children: [/* @__PURE__ */ t(Qe, {}), d.all_categories]
|
|
151
156
|
})
|
|
152
157
|
})
|
|
153
158
|
}),
|
|
154
|
-
|
|
155
|
-
header:
|
|
159
|
+
F && /* @__PURE__ */ t(Me, {
|
|
160
|
+
header: C.header,
|
|
156
161
|
icon: "multiline_chart",
|
|
157
|
-
onClick:
|
|
158
|
-
primaryButton:
|
|
159
|
-
subText:
|
|
162
|
+
onClick: C.onClickHandler,
|
|
163
|
+
primaryButton: C.primaryButton,
|
|
164
|
+
subText: C.description,
|
|
160
165
|
sx: {
|
|
161
|
-
height:
|
|
166
|
+
height: W,
|
|
162
167
|
maxWidth: 432
|
|
163
168
|
}
|
|
164
169
|
}),
|
|
165
|
-
!
|
|
170
|
+
!F && /* @__PURE__ */ l(f, {
|
|
166
171
|
flexDirection: g ? "row" : "column",
|
|
167
172
|
gap: g ? 48 : 16,
|
|
168
|
-
children: [
|
|
173
|
+
children: [p === "Chart" && /* @__PURE__ */ l(Xe, { children: [/* @__PURE__ */ t(w, {
|
|
169
174
|
sx: { width: g ? "68%" : "100%" },
|
|
170
|
-
children: /* @__PURE__ */ t(
|
|
171
|
-
availableHeight:
|
|
175
|
+
children: /* @__PURE__ */ t($e, {
|
|
176
|
+
availableHeight: W,
|
|
172
177
|
minHeight: 450,
|
|
173
|
-
selectedDateRange:
|
|
178
|
+
selectedDateRange: r,
|
|
174
179
|
totals: a
|
|
175
180
|
})
|
|
176
181
|
}), /* @__PURE__ */ l(f, {
|
|
177
182
|
gap: 16,
|
|
178
183
|
sx: { width: g ? "32%" : "100%" },
|
|
179
|
-
children: [R &&
|
|
184
|
+
children: [R && y && k.length > 0 && /* @__PURE__ */ t(je, {
|
|
180
185
|
beats: k,
|
|
181
|
-
onInsightCardClick:
|
|
182
|
-
}), /* @__PURE__ */ t(
|
|
183
|
-
availableHeight:
|
|
184
|
-
onCategoryClick:
|
|
186
|
+
onInsightCardClick: z
|
|
187
|
+
}), /* @__PURE__ */ t(Ke, {
|
|
188
|
+
availableHeight: me,
|
|
189
|
+
onCategoryClick: G
|
|
185
190
|
})]
|
|
186
|
-
})] }),
|
|
191
|
+
})] }), p === "Table" && /* @__PURE__ */ t(w, {
|
|
187
192
|
sx: { width: "100%" },
|
|
188
|
-
children: /* @__PURE__ */ t(
|
|
193
|
+
children: /* @__PURE__ */ t(ze, {
|
|
189
194
|
height: "unset",
|
|
190
|
-
onClickRow:
|
|
191
|
-
selectedCategory:
|
|
192
|
-
selectedDateRange:
|
|
195
|
+
onClickRow: Se,
|
|
196
|
+
selectedCategory: h,
|
|
197
|
+
selectedDateRange: r
|
|
193
198
|
})
|
|
194
199
|
})]
|
|
195
200
|
}),
|
|
196
|
-
/* @__PURE__ */ t(
|
|
201
|
+
/* @__PURE__ */ t(ke, {
|
|
197
202
|
ariaLabelClose: d.close_category_details,
|
|
198
|
-
isOpen: !!
|
|
199
|
-
onClose:
|
|
203
|
+
isOpen: !!_,
|
|
204
|
+
onClose: pe,
|
|
200
205
|
shouldShowHeaderShadow: !0,
|
|
201
|
-
title:
|
|
202
|
-
children:
|
|
206
|
+
title: _?.is_income ? d.category_income : d.category_spending,
|
|
207
|
+
children: _ && /* @__PURE__ */ t(Pe, {})
|
|
203
208
|
})
|
|
204
209
|
]
|
|
205
|
-
}), /* @__PURE__ */ t(
|
|
206
|
-
onClose: () =>
|
|
207
|
-
showConnectWidget:
|
|
208
|
-
title:
|
|
210
|
+
}), /* @__PURE__ */ t(Fe, {
|
|
211
|
+
onClose: () => B(!1),
|
|
212
|
+
showConnectWidget: ge,
|
|
213
|
+
title: re.mini_title
|
|
209
214
|
})]
|
|
210
215
|
});
|
|
211
|
-
},
|
|
216
|
+
}, xt = qe(et);
|
|
212
217
|
export {
|
|
213
|
-
|
|
218
|
+
xt as default
|
|
214
219
|
};
|
|
215
220
|
|
|
216
221
|
//# sourceMappingURL=TrendsWidget.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrendsWidget.es.js","names":[],"sources":["../../src/trends/TrendsWidget.tsx"],"sourcesContent":["import { observer } from 'mobx-react-lite'\nimport React from 'react'\nimport { addSeconds } from 'date-fns/addSeconds'\nimport { differenceInCalendarMonths } from 'date-fns/differenceInCalendarMonths'\n\nimport { DateRange } from '@mui/x-date-pickers-pro'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Stack from '@mui/material/Stack'\n\nimport { ArrowBack } from '@mxenabled/mx-icons'\n\nimport {\n ANALYTICS_EVENTS,\n ConnectDrawer,\n Drawer,\n EmptyState,\n Loader,\n WidgetContainer,\n useAccountStore,\n useAppConfig,\n useCategoryStore,\n useEvent,\n useGlobalCopyStore,\n useGlobalStore,\n useGlobalUiStore,\n useInsightsEnabled,\n useScreenSize,\n useTransactionStore,\n useTrendsStore,\n useWidgetLoadTimer,\n} from '../common'\nimport { type Beat, type DetailedCategoryWithTransactions, Transaction, WidgetProps } from '../core'\n\nimport DateRangeText from './components/DateRangeText'\nimport TrendsViewToggle, { type TrendsTabValue } from './components/TrendsViewToggle'\nimport TrendsTableContainer from './components/TrendsTableContainer'\nimport CategoriesList from './components/CategoriesList'\nimport InsightsCarousel from './components/TrendsInsights'\nimport { CategoryDetails } from './components/CategoryDetails'\nimport TrendsChartTotals from './components/TrendsChartTotals'\nimport { TRENDS_ANALYTICS_METADATA } from './constants/TrendsWidget'\nimport { TRENDS_BEAT_TEMPLATES } from './constants/TrendsInsights'\nimport { buildEmptyStateProps } from './utils/TrendsWidget'\n\ninterface TrendsWidgetProps extends WidgetProps {\n onInsightCardClick?: (beat?: Beat) => void\n}\n\nconst TrendsWidget: React.FC<TrendsWidgetProps> = ({\n onBackClick,\n onMenuClick,\n onInsightCardClick,\n sx,\n}) => {\n const { onEvent } = useEvent()\n const { config } = useAppConfig()\n const { isDesktop, isLargeDesktop, isMobile } = useScreenSize()\n const { isAccountDataLoaded, loadAccountData, visibleAccounts: accounts } = useAccountStore()\n const {\n categoriesLoaded,\n loadCategories,\n monthlyCategoryTotals,\n monthlyTotalsLoaded,\n reloadCategoryTotals,\n } = useCategoryStore()\n const { isTransactionDataLoaded, loadTransactionData, setFilter } = useTransactionStore()\n const {\n isTransactionStoreIdle,\n selectedCategoryData,\n selectedDateRange,\n setSelectedCategoryData,\n setSelectedDateRange,\n } = useTrendsStore()\n const { isInitialized, selectedAccounts, selectedAccountGuids } = useGlobalUiStore()\n const { trends: copy, connect: connectCopy } = useGlobalCopyStore()\n const { beatStore } = useGlobalStore()\n const insightsEnabled = useInsightsEnabled()\n\n // allows clients to disable insights in trends while still enabling insights\n const showInsights = insightsEnabled && config.show_insights_widget_in_master\n const isLargeLayout = isDesktop || isLargeDesktop\n\n const [isWidgetInitialized, setIsWidgetInitialized] = React.useState(false)\n const [isBeatsLoaded, setIsBeatsLoaded] = React.useState(false)\n const [selectedTab, setSelectedTab] = React.useState<TrendsTabValue>('Chart')\n const [selectedCategory, setSelectedCategory] = React.useState('')\n\n // Control responsive chart height based on available widget iframe viewport height\n const [viewportHeight, setViewportHeight] = React.useState(window.innerHeight)\n const insightsPadding = isLargeLayout ? 56 : 0\n const availableChartHeight = viewportHeight - 208 // unavailable height on desktop\n const availableListHeight = viewportHeight - (showInsights ? 550 + insightsPadding : 266)\n\n const [showConnectWidget, setShowConnectWidget] = React.useState(false)\n const hasAggregation = config.show_connections_widget_in_master\n\n // iOS Safari doesn't scroll off-screen elements into view on forward Tab\n // inside this non-document scroll container. `:focus-visible` limits the\n // scroll to keyboard focus so pointer clicks on clipped rows don't jump.\n // `block: 'nearest'` is a no-op when already visible. Callback ref (not\n // useEffect) because the content mounts after the loading gate.\n const cleanupFocusScroll = React.useRef<(() => void) | undefined>(undefined)\n const contentRef = React.useCallback((node: HTMLDivElement | null) => {\n cleanupFocusScroll.current?.()\n cleanupFocusScroll.current = undefined\n\n if (!node) return\n\n const handleFocusIn = (event: FocusEvent) => {\n if (event.target instanceof HTMLElement && event.target.matches(':focus-visible')) {\n event.target.scrollIntoView({ block: 'nearest' })\n }\n }\n\n node.addEventListener('focusin', handleFocusIn)\n cleanupFocusScroll.current = () => node.removeEventListener('focusin', handleFocusIn)\n }, [])\n\n useWidgetLoadTimer({\n widgetName: 'TrendsWidget',\n isLoaded: isWidgetInitialized,\n })\n const filteredBeats = React.useMemo(() => {\n if (!isBeatsLoaded) return []\n return beatStore.getFilteredBeats({ templates: TRENDS_BEAT_TEMPLATES })\n }, [isBeatsLoaded])\n\n React.useEffect(() => {\n // Setup resize listener for responsive chart and list heights\n const handleResize = () => setViewportHeight(window.innerHeight)\n window.addEventListener('resize', handleResize)\n\n if (!isAccountDataLoaded) {\n loadAccountData().finally()\n }\n if (!categoriesLoaded) {\n loadCategories().finally()\n }\n if (!isTransactionDataLoaded) {\n loadTransactionData().finally()\n }\n\n return () => window.removeEventListener('resize', handleResize)\n }, [])\n\n // Initialize monthlyCategoryTotals, and reload on account or date range change.\n // Depends on primitives rather than `selectedAccounts` / `selectedDateRange`,\n // whose object identities change even when the values do not. (MEW-3910)\n //\n // Both totals are loaded in parallel rather than chaining monthly inside the\n // date-range `.then()`. Chaining defers the monthly request until the first\n // resolves, so a slow superseded range would claim the newest monthly request\n // token and write stale data over the current range. (MEW-3910)\n React.useEffect(() => {\n if (isInitialized && isAccountDataLoaded) {\n beatStore.loadBeats().finally(() => setIsBeatsLoaded(true))\n reloadCategoryTotals(\n selectedAccounts,\n selectedDateRange.start,\n selectedDateRange.end,\n ).finally(() => {\n setIsWidgetInitialized(true)\n onEvent(ANALYTICS_EVENTS.TRENDS_LOAD_WIDGET, {\n ...TRENDS_ANALYTICS_METADATA,\n time_period: `${differenceInCalendarMonths(selectedDateRange.end, selectedDateRange.start)}M`,\n })\n })\n }\n }, [\n isInitialized,\n isAccountDataLoaded,\n selectedAccountGuids.join(','),\n selectedDateRange.start.getTime(),\n selectedDateRange.end.getTime(),\n ])\n // Totals are refetched on every range/account change, so their identity marks a new filter.\n // Tracking it stops a failed load from re-firing setFilter every time the store goes idle.\n const filteredTotalsRef = React.useRef<typeof monthlyCategoryTotals | null>(null)\n\n React.useEffect(() => {\n // Wait for the transaction store to go idle so the selected range load isn't dropped\n if (\n monthlyCategoryTotals.length !== 0 &&\n isTransactionStoreIdle &&\n filteredTotalsRef.current !== monthlyCategoryTotals\n ) {\n filteredTotalsRef.current = monthlyCategoryTotals\n const transactionFilter = {\n accounts: selectedAccountGuids,\n dateRange: { start: selectedDateRange.start, end: selectedDateRange.end },\n }\n\n // this filter sets sortedTransactions in the transaction store\n setFilter({\n ...transactionFilter,\n custom: (t: Transaction) =>\n Boolean(\n monthlyCategoryTotals.find(\n (cat) =>\n cat.top_level_category_guid === t.top_level_category_guid ||\n cat.category_guid === t.category_guid,\n ),\n ),\n })\n }\n }, [monthlyCategoryTotals, isTransactionStoreIdle])\n\n const handleDetailOpen = (selected: DetailedCategoryWithTransactions) => {\n setSelectedCategoryData(selected)\n }\n\n const handleDetailClose = () => {\n setSelectedCategoryData(null)\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_BACK, TRENDS_ANALYTICS_METADATA)\n }\n\n const handleEmptyStateClick = () => {\n setShowConnectWidget(true)\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_CONNECT_ACCOUNTS)\n }\n\n const onTabChange = (_event: React.SyntheticEvent, newValue: TrendsTabValue) => {\n setSelectedTab(newValue ?? selectedTab)\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_TOGGLE_VIEW, {\n ...TRENDS_ANALYTICS_METADATA,\n toggleView: newValue,\n })\n }\n\n const onRangeChanged = (dateRange: DateRange<Date>) => {\n const startDate = addSeconds(dateRange?.[0] as Date, 1)\n const endDate = dateRange?.[1] as Date\n const months = differenceInCalendarMonths(endDate, startDate)\n\n setSelectedDateRange({ start: startDate, end: endDate })\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_TIME_WINDOW, {\n ...TRENDS_ANALYTICS_METADATA,\n time_period: months + 'M',\n })\n }\n\n const onAccountsFilterClick = (selectedAccountGuids?: string[]) => {\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_FILTER, {\n ...TRENDS_ANALYTICS_METADATA,\n filterValue: selectedAccountGuids,\n })\n }\n\n const onClickRow = (guid: string) => {\n setSelectedCategory(guid)\n }\n\n const onViewAllCategories = () => {\n setSelectedCategory('')\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_ALL_CATEGORIES, TRENDS_ANALYTICS_METADATA)\n }\n\n if (!isInitialized || !isAccountDataLoaded || !isWidgetInitialized || !monthlyTotalsLoaded)\n return <Loader />\n\n const isEmptyTrendsState = accounts.length === 0 || monthlyCategoryTotals.length === 0\n\n const emptyStateProps = buildEmptyStateProps(copy, hasAggregation, handleEmptyStateClick)\n\n return (\n <WidgetContainer\n calendarActions={{ onRangeChanged }}\n dateRange={selectedDateRange}\n dateRangeVariant={isMobile ? 'timeframetabs' : 'timeframebuttons'}\n onAccountsFilterClick={onAccountsFilterClick}\n onBackClick={onBackClick}\n onMenuClick={onMenuClick}\n sx={sx}\n title={copy.title}\n >\n <Stack\n ref={contentRef}\n sx={{\n px: isMobile ? 0 : 48,\n }}\n >\n <Stack\n flexDirection=\"row\"\n justifyContent=\"space-between\"\n sx={{ px: isMobile ? 16 : 0, py: 16 }}\n >\n <DateRangeText\n selectedCategoryGuid={selectedCategory}\n selectedDateRange={selectedDateRange}\n />\n <TrendsViewToggle onTabChange={onTabChange} selectedTab={selectedTab} />\n </Stack>\n <Stack alignItems=\"center\" flexDirection=\"row\" sx={{ pb: 8 }} width=\"100%\">\n <Box flexGrow={1}>\n {selectedCategory && (\n <Button onClick={onViewAllCategories} sx={{ p: 0, pr: 5 }}>\n <ArrowBack />\n {copy.all_categories}\n </Button>\n )}\n </Box>\n </Stack>\n\n {isEmptyTrendsState && (\n <EmptyState\n header={emptyStateProps.header}\n icon=\"multiline_chart\"\n onClick={emptyStateProps.onClickHandler}\n primaryButton={emptyStateProps.primaryButton}\n subText={emptyStateProps.description}\n sx={{ height: availableChartHeight, maxWidth: 432 }}\n />\n )}\n {!isEmptyTrendsState && (\n <Stack flexDirection={isLargeLayout ? 'row' : 'column'} gap={isLargeLayout ? 48 : 16}>\n {selectedTab === 'Chart' && (\n <>\n <Box sx={{ width: isLargeLayout ? '68%' : '100%' }}>\n <TrendsChartTotals\n availableHeight={availableChartHeight}\n minHeight={450}\n selectedDateRange={selectedDateRange}\n totals={monthlyCategoryTotals}\n />\n </Box>\n <Stack gap={16} sx={{ width: isLargeLayout ? '32%' : '100%' }}>\n {/* Only show insights enabled and there are beats to show */}\n {showInsights && isBeatsLoaded && filteredBeats.length > 0 && (\n <InsightsCarousel\n beats={filteredBeats}\n onInsightCardClick={onInsightCardClick}\n />\n )}\n <CategoriesList\n availableHeight={availableListHeight}\n onCategoryClick={handleDetailOpen}\n />\n </Stack>\n </>\n )}\n {selectedTab === 'Table' && (\n <Box sx={{ width: '100%' }}>\n <TrendsTableContainer\n height=\"unset\"\n onClickRow={onClickRow}\n selectedCategory={selectedCategory}\n selectedDateRange={selectedDateRange}\n />\n </Box>\n )}\n </Stack>\n )}\n <Drawer\n ariaLabelClose={copy.close_category_details}\n isOpen={Boolean(selectedCategoryData)}\n onClose={handleDetailClose}\n shouldShowHeaderShadow={true}\n title={selectedCategoryData?.is_income ? copy.category_income : copy.category_spending}\n >\n {selectedCategoryData && <CategoryDetails />}\n </Drawer>\n </Stack>\n\n <ConnectDrawer\n onClose={() => setShowConnectWidget(false)}\n showConnectWidget={showConnectWidget}\n title={connectCopy.mini_title}\n />\n </WidgetContainer>\n )\n}\n\nexport default observer(TrendsWidget)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrendsWidget.es.js","names":[],"sources":["../../src/trends/TrendsWidget.tsx"],"sourcesContent":["import { observer } from 'mobx-react-lite'\nimport React from 'react'\nimport { addSeconds } from 'date-fns/addSeconds'\nimport { differenceInCalendarMonths } from 'date-fns/differenceInCalendarMonths'\n\nimport { DateRange } from '@mui/x-date-pickers-pro'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Stack from '@mui/material/Stack'\n\nimport { ArrowBack } from '@mxenabled/mx-icons'\n\nimport {\n ANALYTICS_EVENTS,\n ConnectDrawer,\n Drawer,\n EmptyState,\n Loader,\n WidgetContainer,\n useAccountStore,\n useAppConfig,\n useCategoryStore,\n useEvent,\n useGlobalCopyStore,\n useGlobalStore,\n useGlobalUiStore,\n useInsightsEnabled,\n useScreenSize,\n useTransactionStore,\n useTrendsStore,\n useWidgetLoadTimer,\n} from '../common'\nimport { type Beat, type DetailedCategoryWithTransactions, Transaction, WidgetProps } from '../core'\n\nimport DateRangeText from './components/DateRangeText'\nimport TrendsViewToggle, { type TrendsTabValue } from './components/TrendsViewToggle'\nimport TrendsTableContainer from './components/TrendsTableContainer'\nimport CategoriesList from './components/CategoriesList'\nimport InsightsCarousel from './components/TrendsInsights'\nimport { CategoryDetails } from './components/CategoryDetails'\nimport TrendsChartTotals from './components/TrendsChartTotals'\nimport { TRENDS_ANALYTICS_METADATA } from './constants/TrendsWidget'\nimport { TRENDS_BEAT_TEMPLATES } from './constants/TrendsInsights'\nimport { buildEmptyStateProps } from './utils/TrendsWidget'\n\ninterface TrendsWidgetProps extends WidgetProps {\n onInsightCardClick?: (beat?: Beat) => void\n}\n\nconst TrendsWidget: React.FC<TrendsWidgetProps> = ({\n onBackClick,\n onMenuClick,\n onInsightCardClick,\n sx,\n}) => {\n const { onEvent } = useEvent()\n const { config } = useAppConfig()\n const { isDesktop, isLargeDesktop, isMobile } = useScreenSize()\n const { isAccountDataLoaded, loadAccountData, visibleAccounts: accounts } = useAccountStore()\n const {\n categoriesLoaded,\n loadCategories,\n monthlyCategoryTotals,\n monthlyTotalsLoaded,\n reloadCategoryTotals,\n } = useCategoryStore()\n const { isTransactionDataLoaded, loadTransactionData, setFilter } = useTransactionStore()\n const {\n getCategoryWithTransactions,\n isTransactionStoreIdle,\n selectedCategoryData,\n selectedDateRange,\n setSelectedCategoryData,\n setSelectedDateRange,\n } = useTrendsStore()\n const { isInitialized, selectedAccounts, selectedAccountGuids } = useGlobalUiStore()\n const { trends: copy, connect: connectCopy } = useGlobalCopyStore()\n const { beatStore, captureException } = useGlobalStore()\n const insightsEnabled = useInsightsEnabled()\n\n // allows clients to disable insights in trends while still enabling insights\n const showInsights = insightsEnabled && config.show_insights_widget_in_master\n const isLargeLayout = isDesktop || isLargeDesktop\n\n const [isWidgetInitialized, setIsWidgetInitialized] = React.useState(false)\n const [isBeatsLoaded, setIsBeatsLoaded] = React.useState(false)\n const [selectedTab, setSelectedTab] = React.useState<TrendsTabValue>('Chart')\n const [selectedCategory, setSelectedCategory] = React.useState('')\n\n // Control responsive chart height based on available widget iframe viewport height\n const [viewportHeight, setViewportHeight] = React.useState(window.innerHeight)\n const insightsPadding = isLargeLayout ? 56 : 0\n const availableChartHeight = viewportHeight - 208 // unavailable height on desktop\n const availableListHeight = viewportHeight - (showInsights ? 550 + insightsPadding : 266)\n\n const [showConnectWidget, setShowConnectWidget] = React.useState(false)\n const hasAggregation = config.show_connections_widget_in_master\n\n // iOS Safari doesn't scroll off-screen elements into view on forward Tab\n // inside this non-document scroll container. `:focus-visible` limits the\n // scroll to keyboard focus so pointer clicks on clipped rows don't jump.\n // `block: 'nearest'` is a no-op when already visible. Callback ref (not\n // useEffect) because the content mounts after the loading gate.\n const cleanupFocusScroll = React.useRef<(() => void) | undefined>(undefined)\n const contentRef = React.useCallback((node: HTMLDivElement | null) => {\n cleanupFocusScroll.current?.()\n cleanupFocusScroll.current = undefined\n\n if (!node) return\n\n const handleFocusIn = (event: FocusEvent) => {\n if (event.target instanceof HTMLElement && event.target.matches(':focus-visible')) {\n event.target.scrollIntoView({ block: 'nearest' })\n }\n }\n\n node.addEventListener('focusin', handleFocusIn)\n cleanupFocusScroll.current = () => node.removeEventListener('focusin', handleFocusIn)\n }, [])\n\n useWidgetLoadTimer({\n widgetName: 'TrendsWidget',\n isLoaded: isWidgetInitialized,\n })\n const filteredBeats = React.useMemo(() => {\n if (!isBeatsLoaded) return []\n return beatStore.getFilteredBeats({ templates: TRENDS_BEAT_TEMPLATES })\n }, [isBeatsLoaded])\n\n React.useEffect(() => {\n // Setup resize listener for responsive chart and list heights\n const handleResize = () => setViewportHeight(window.innerHeight)\n window.addEventListener('resize', handleResize)\n\n if (!isAccountDataLoaded) {\n loadAccountData().finally()\n }\n if (!categoriesLoaded) {\n loadCategories().finally()\n }\n if (!isTransactionDataLoaded) {\n loadTransactionData().finally()\n }\n\n return () => window.removeEventListener('resize', handleResize)\n }, [])\n\n // Initialize monthlyCategoryTotals, and reload on account or date range change.\n // Depends on primitives rather than `selectedAccounts` / `selectedDateRange`,\n // whose object identities change even when the values do not. (MEW-3910)\n //\n // Both totals are loaded in parallel rather than chaining monthly inside the\n // date-range `.then()`. Chaining defers the monthly request until the first\n // resolves, so a slow superseded range would claim the newest monthly request\n // token and write stale data over the current range. (MEW-3910)\n React.useEffect(() => {\n if (isInitialized && isAccountDataLoaded) {\n beatStore.loadBeats().finally(() => setIsBeatsLoaded(true))\n reloadCategoryTotals(\n selectedAccounts,\n selectedDateRange.start,\n selectedDateRange.end,\n ).finally(() => {\n setIsWidgetInitialized(true)\n onEvent(ANALYTICS_EVENTS.TRENDS_LOAD_WIDGET, {\n ...TRENDS_ANALYTICS_METADATA,\n time_period: `${differenceInCalendarMonths(selectedDateRange.end, selectedDateRange.start)}M`,\n })\n })\n }\n }, [\n isInitialized,\n isAccountDataLoaded,\n selectedAccountGuids.join(','),\n selectedDateRange.start.getTime(),\n selectedDateRange.end.getTime(),\n ])\n // Totals are refetched on every range/account change, so their identity marks a new filter.\n // Tracking it stops a failed load from re-firing setFilter every time the store goes idle.\n const filteredTotalsRef = React.useRef<typeof monthlyCategoryTotals | null>(null)\n\n React.useEffect(() => {\n // Wait for the transaction store to go idle so the selected range load isn't dropped\n if (\n monthlyCategoryTotals.length !== 0 &&\n isTransactionStoreIdle &&\n filteredTotalsRef.current !== monthlyCategoryTotals\n ) {\n filteredTotalsRef.current = monthlyCategoryTotals\n const transactionFilter = {\n accounts: selectedAccountGuids,\n dateRange: { start: selectedDateRange.start, end: selectedDateRange.end },\n }\n\n // this filter sets sortedTransactions in the transaction store\n setFilter({\n ...transactionFilter,\n custom: (t: Transaction) =>\n Boolean(\n monthlyCategoryTotals.find(\n (cat) =>\n cat.top_level_category_guid === t.top_level_category_guid ||\n cat.category_guid === t.category_guid,\n ),\n ),\n })\n }\n }, [monthlyCategoryTotals, isTransactionStoreIdle])\n\n const handleDetailOpen = (selected: DetailedCategoryWithTransactions) => {\n setSelectedCategoryData(selected)\n }\n\n const handleDetailClose = () => {\n setSelectedCategoryData(null)\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_BACK, TRENDS_ANALYTICS_METADATA)\n }\n\n const handleEmptyStateClick = () => {\n setShowConnectWidget(true)\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_CONNECT_ACCOUNTS)\n }\n\n const onTabChange = (_event: React.SyntheticEvent, newValue: TrendsTabValue) => {\n setSelectedTab(newValue ?? selectedTab)\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_TOGGLE_VIEW, {\n ...TRENDS_ANALYTICS_METADATA,\n toggleView: newValue,\n })\n }\n\n const onRangeChanged = (dateRange: DateRange<Date>) => {\n const startDate = addSeconds(dateRange?.[0] as Date, 1)\n const endDate = dateRange?.[1] as Date\n const months = differenceInCalendarMonths(endDate, startDate)\n\n setSelectedDateRange({ start: startDate, end: endDate })\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_TIME_WINDOW, {\n ...TRENDS_ANALYTICS_METADATA,\n time_period: months + 'M',\n })\n }\n\n const onAccountsFilterClick = (selectedAccountGuids?: string[]) => {\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_FILTER, {\n ...TRENDS_ANALYTICS_METADATA,\n filterValue: selectedAccountGuids,\n })\n }\n\n // Top-level table rows drill into their sub-categories; once drilled in, a\n // sub-category row opens the same category details drawer the chart view's\n // category list reaches (table view parity). The non-navigable total and\n // \"General [Parent]\" rows are filtered out in TrendsTable.\n const onClickRow = (guid: string) => {\n if (!selectedCategory) {\n setSelectedCategory(guid)\n return\n }\n const category = getCategoryWithTransactions(guid)\n if (category) handleDetailOpen(category)\n // Every navigable sub-category row resolves today; log a miss so a future\n // regression on a cursor:pointer row isn't a silent dead click.\n else captureException(new Error(`TrendsWidget: no category found for guid ${guid}`))\n }\n\n const onViewAllCategories = () => {\n setSelectedCategory('')\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_ALL_CATEGORIES, TRENDS_ANALYTICS_METADATA)\n }\n\n if (!isInitialized || !isAccountDataLoaded || !isWidgetInitialized || !monthlyTotalsLoaded)\n return <Loader />\n\n const isEmptyTrendsState = accounts.length === 0 || monthlyCategoryTotals.length === 0\n\n const emptyStateProps = buildEmptyStateProps(copy, hasAggregation, handleEmptyStateClick)\n\n return (\n <WidgetContainer\n calendarActions={{ onRangeChanged }}\n dateRange={selectedDateRange}\n dateRangeVariant={isMobile ? 'timeframetabs' : 'timeframebuttons'}\n onAccountsFilterClick={onAccountsFilterClick}\n onBackClick={onBackClick}\n onMenuClick={onMenuClick}\n sx={sx}\n title={copy.title}\n >\n <Stack\n ref={contentRef}\n sx={{\n px: isMobile ? 0 : 48,\n }}\n >\n <Stack\n flexDirection=\"row\"\n justifyContent=\"space-between\"\n sx={{ px: isMobile ? 16 : 0, py: 16 }}\n >\n <DateRangeText\n selectedCategoryGuid={selectedCategory}\n selectedDateRange={selectedDateRange}\n />\n <TrendsViewToggle onTabChange={onTabChange} selectedTab={selectedTab} />\n </Stack>\n <Stack alignItems=\"center\" flexDirection=\"row\" sx={{ pb: 8 }} width=\"100%\">\n <Box flexGrow={1}>\n {selectedCategory && (\n <Button onClick={onViewAllCategories} sx={{ p: 0, pr: 5 }}>\n <ArrowBack />\n {copy.all_categories}\n </Button>\n )}\n </Box>\n </Stack>\n\n {isEmptyTrendsState && (\n <EmptyState\n header={emptyStateProps.header}\n icon=\"multiline_chart\"\n onClick={emptyStateProps.onClickHandler}\n primaryButton={emptyStateProps.primaryButton}\n subText={emptyStateProps.description}\n sx={{ height: availableChartHeight, maxWidth: 432 }}\n />\n )}\n {!isEmptyTrendsState && (\n <Stack flexDirection={isLargeLayout ? 'row' : 'column'} gap={isLargeLayout ? 48 : 16}>\n {selectedTab === 'Chart' && (\n <>\n <Box sx={{ width: isLargeLayout ? '68%' : '100%' }}>\n <TrendsChartTotals\n availableHeight={availableChartHeight}\n minHeight={450}\n selectedDateRange={selectedDateRange}\n totals={monthlyCategoryTotals}\n />\n </Box>\n <Stack gap={16} sx={{ width: isLargeLayout ? '32%' : '100%' }}>\n {/* Only show insights enabled and there are beats to show */}\n {showInsights && isBeatsLoaded && filteredBeats.length > 0 && (\n <InsightsCarousel\n beats={filteredBeats}\n onInsightCardClick={onInsightCardClick}\n />\n )}\n <CategoriesList\n availableHeight={availableListHeight}\n onCategoryClick={handleDetailOpen}\n />\n </Stack>\n </>\n )}\n {selectedTab === 'Table' && (\n <Box sx={{ width: '100%' }}>\n <TrendsTableContainer\n height=\"unset\"\n onClickRow={onClickRow}\n selectedCategory={selectedCategory}\n selectedDateRange={selectedDateRange}\n />\n </Box>\n )}\n </Stack>\n )}\n <Drawer\n ariaLabelClose={copy.close_category_details}\n isOpen={Boolean(selectedCategoryData)}\n onClose={handleDetailClose}\n shouldShowHeaderShadow={true}\n title={selectedCategoryData?.is_income ? copy.category_income : copy.category_spending}\n >\n {selectedCategoryData && <CategoryDetails />}\n </Drawer>\n </Stack>\n\n <ConnectDrawer\n onClose={() => setShowConnectWidget(false)}\n showConnectWidget={showConnectWidget}\n title={connectCopy.mini_title}\n />\n </WidgetContainer>\n )\n}\n\nexport default observer(TrendsWidget)\n"],"mappings":"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"}
|
|
@@ -38,12 +38,10 @@ var G = ({ selectedDateRange: m, selectedCategory: l, onClickRow: c, height: w =
|
|
|
38
38
|
children: [(!o || o && o.is_income) && /* @__PURE__ */ g(p, {
|
|
39
39
|
columns: d,
|
|
40
40
|
onRowClick: c,
|
|
41
|
-
parentCategory: o,
|
|
42
41
|
rows: h
|
|
43
42
|
}), (!o || o && !o.is_income) && /* @__PURE__ */ g(p, {
|
|
44
43
|
columns: d,
|
|
45
44
|
onRowClick: c,
|
|
46
|
-
parentCategory: o,
|
|
47
45
|
rows: u,
|
|
48
46
|
totalRow: y
|
|
49
47
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrendsTableContainer.es.js","names":[],"sources":["../../../src/trends/components/TrendsTableContainer.tsx"],"sourcesContent":["import React from 'react'\n\nimport Stack from '@mui/material/Stack'\n\nimport { useCategoryStore, useGlobalCopyStore } from '../../common'\n\nimport {\n generateMonthHeaders,\n getTrendsTableColumns,\n getTrendsTableRows,\n} from '../utils/TrendsTable'\n\nimport { TrendsTable } from './trendstable/TrendsTable'\n\ninterface TrendsTableProps {\n sx?: any\n height?: number | string\n selectedDateRange: { start: Date; end: Date }\n selectedCategory?: string\n onClickRow?: (category: string) => void\n}\n\nconst TrendsTableContainer: React.FC<TrendsTableProps> = ({\n selectedDateRange,\n selectedCategory,\n onClickRow,\n height = '100%',\n sx = {},\n}) => {\n const { trends: copy } = useGlobalCopyStore()\n const { trendsCategories } = useCategoryStore()\n\n // Generate all headers for the selected date range\n const monthHeaders = React.useMemo(() => {\n return generateMonthHeaders(selectedDateRange.start, selectedDateRange.end)\n }, [selectedDateRange])\n\n const columns = React.useMemo(() => {\n return getTrendsTableColumns(monthHeaders, copy)\n }, [monthHeaders, copy])\n\n const parentCategory = React.useMemo(() => {\n if (selectedCategory) {\n return trendsCategories.find((category) => category.guid === selectedCategory)\n }\n return undefined\n }, [selectedCategory, trendsCategories])\n\n const [incomeRows, spendingRows] = React.useMemo(() => {\n let categories = trendsCategories\n if (parentCategory) {\n categories = parentCategory.subCategories.filter((c) =>\n c.monthlyAmounts.some((ma) => ma.amount !== 0),\n )\n }\n\n return [\n getTrendsTableRows(categories, copy, true, monthHeaders, parentCategory),\n getTrendsTableRows(categories, copy, false, monthHeaders, parentCategory),\n ]\n }, [trendsCategories, monthHeaders, parentCategory, copy])\n\n const totalSpendingRow = {\n id: 'total',\n category: copy.table_column_total,\n category_guid: 'total',\n isParent: false,\n top_level_category_guid: 'total',\n total: spendingRows.reduce((sum, row) => sum + row.total, 0),\n monthlyAmounts: spendingRows.reduce(\n (amounts, row) => {\n Object.entries(row.monthlyAmounts).forEach(([month, amount]) => {\n amounts[month] = (amounts[month] || 0) + amount\n })\n return amounts\n },\n {} as Record<string, number>,\n ),\n }\n\n return (\n <Stack\n gap={24}\n sx={{ height: height, width: '100%', overflowX: 'auto', boxShadow: 'none', ...sx }}\n >\n {(!parentCategory || (parentCategory && parentCategory.is_income)) && (\n <TrendsTable
|
|
1
|
+
{"version":3,"file":"TrendsTableContainer.es.js","names":[],"sources":["../../../src/trends/components/TrendsTableContainer.tsx"],"sourcesContent":["import React from 'react'\n\nimport Stack from '@mui/material/Stack'\n\nimport { useCategoryStore, useGlobalCopyStore } from '../../common'\n\nimport {\n generateMonthHeaders,\n getTrendsTableColumns,\n getTrendsTableRows,\n} from '../utils/TrendsTable'\n\nimport { TrendsTable } from './trendstable/TrendsTable'\n\ninterface TrendsTableProps {\n sx?: any\n height?: number | string\n selectedDateRange: { start: Date; end: Date }\n selectedCategory?: string\n onClickRow?: (category: string) => void\n}\n\nconst TrendsTableContainer: React.FC<TrendsTableProps> = ({\n selectedDateRange,\n selectedCategory,\n onClickRow,\n height = '100%',\n sx = {},\n}) => {\n const { trends: copy } = useGlobalCopyStore()\n const { trendsCategories } = useCategoryStore()\n\n // Generate all headers for the selected date range\n const monthHeaders = React.useMemo(() => {\n return generateMonthHeaders(selectedDateRange.start, selectedDateRange.end)\n }, [selectedDateRange])\n\n const columns = React.useMemo(() => {\n return getTrendsTableColumns(monthHeaders, copy)\n }, [monthHeaders, copy])\n\n const parentCategory = React.useMemo(() => {\n if (selectedCategory) {\n return trendsCategories.find((category) => category.guid === selectedCategory)\n }\n return undefined\n }, [selectedCategory, trendsCategories])\n\n const [incomeRows, spendingRows] = React.useMemo(() => {\n let categories = trendsCategories\n if (parentCategory) {\n categories = parentCategory.subCategories.filter((c) =>\n c.monthlyAmounts.some((ma) => ma.amount !== 0),\n )\n }\n\n return [\n getTrendsTableRows(categories, copy, true, monthHeaders, parentCategory),\n getTrendsTableRows(categories, copy, false, monthHeaders, parentCategory),\n ]\n }, [trendsCategories, monthHeaders, parentCategory, copy])\n\n const totalSpendingRow = {\n id: 'total',\n category: copy.table_column_total,\n category_guid: 'total',\n isParent: false,\n top_level_category_guid: 'total',\n total: spendingRows.reduce((sum, row) => sum + row.total, 0),\n monthlyAmounts: spendingRows.reduce(\n (amounts, row) => {\n Object.entries(row.monthlyAmounts).forEach(([month, amount]) => {\n amounts[month] = (amounts[month] || 0) + amount\n })\n return amounts\n },\n {} as Record<string, number>,\n ),\n }\n\n return (\n <Stack\n gap={24}\n sx={{ height: height, width: '100%', overflowX: 'auto', boxShadow: 'none', ...sx }}\n >\n {(!parentCategory || (parentCategory && parentCategory.is_income)) && (\n <TrendsTable columns={columns} onRowClick={onClickRow} rows={incomeRows} />\n )}\n {(!parentCategory || (parentCategory && !parentCategory.is_income)) && (\n <TrendsTable\n columns={columns}\n onRowClick={onClickRow}\n rows={spendingRows}\n totalRow={totalSpendingRow}\n />\n )}\n </Stack>\n )\n}\n\nexport default TrendsTableContainer\n"],"mappings":";;;;;;AAsBA,IAAM,IAAA,CAAoD,EACxD,mBAAA,GACA,kBAAA,GACA,YAAA,GACA,QAAA,IAAS,QACT,IAAA,IAAK,CAAC,EAAA,MACF;AACJ,QAAM,EAAE,QAAQ,EAAA,IAAS,EAAmB,GACtC,EAAE,kBAAA,EAAA,IAAqB,EAAiB,GAGxC,IAAe,EAAM,QAAA,MAClB,EAAqB,EAAkB,OAAO,EAAkB,GAAG,GACzE,CAAC,CAAiB,CAAC,GAEhB,IAAU,EAAM,QAAA,MACb,EAAsB,GAAc,CAAI,GAC9C,CAAC,GAAc,CAAI,CAAC,GAEjB,IAAiB,EAAM,QAAA,MAAc;AACzC,QAAI,EACF,QAAO,EAAiB,KAAA,CAAM,MAAa,EAAS,SAAS,CAAgB;AAAA,EAGjF,GAAG,CAAC,GAAkB,CAAgB,CAAC,GAEjC,CAAC,GAAY,CAAA,IAAgB,EAAM,QAAA,MAAc;AACrD,QAAI,IAAa;AACjB,WAAI,MACF,IAAa,EAAe,cAAc,OAAA,CAAQ,MAChD,EAAE,eAAe,KAAA,CAAM,MAAO,EAAG,WAAW,CAAC,CAC/C,IAGK,CACL,EAAmB,GAAY,GAAM,IAAM,GAAc,CAAc,GACvE,EAAmB,GAAY,GAAM,IAAO,GAAc,CAAc,CAC1E;AAAA,EACF,GAAG;AAAA,IAAC;AAAA,IAAkB;AAAA,IAAc;AAAA,IAAgB;AAAA,EAAI,CAAC,GAEnD,IAAmB;AAAA,IACvB,IAAI;AAAA,IACJ,UAAU,EAAK;AAAA,IACf,eAAe;AAAA,IACf,UAAU;AAAA,IACV,yBAAyB;AAAA,IACzB,OAAO,EAAa,OAAA,CAAQ,GAAK,MAAQ,IAAM,EAAI,OAAO,CAAC;AAAA,IAC3D,gBAAgB,EAAa,OAAA,CAC1B,GAAS,OACR,OAAO,QAAQ,EAAI,cAAc,EAAE,QAAA,CAAS,CAAC,GAAO,CAAA,MAAY;AAC9D,MAAA,EAAQ,CAAA,KAAU,EAAQ,CAAA,KAAU,KAAK;AAAA,IAC3C,CAAC,GACM,IAET,CAAC,CACH;AAAA,EACF;AAEA,SACE,gBAAA,EAAC,GAAD;AAAA,IACE,KAAK;AAAA,IACL,IAAI;AAAA,MAAU,QAAA;AAAA,MAAQ,OAAO;AAAA,MAAQ,WAAW;AAAA,MAAQ,WAAW;AAAA,MAAQ,GAAG;AAAA,IAAG;AAAA,IAFnF,UAAA,EAII,CAAC,KAAmB,KAAkB,EAAe,cACrD,gBAAA,EAAC,GAAD;AAAA,MAAsB,SAAA;AAAA,MAAS,YAAY;AAAA,MAAY,MAAM;AAAA,IAAa,CAAA,IAE1E,CAAC,KAAmB,KAAkB,CAAC,EAAe,cACtD,gBAAA,EAAC,GAAD;AAAA,MACW,SAAA;AAAA,MACT,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,UAAU;AAAA,IACX,CAAA,CAEE;AAAA;AAEX"}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { DetailedCategory } from '../../../core';
|
|
2
1
|
import { GridColDef } from '@mui/x-data-grid-pro';
|
|
3
2
|
import { TrendsTableRow } from '../../utils/TrendsTable';
|
|
4
3
|
interface TrendsTableProps {
|
|
5
4
|
columns: GridColDef[];
|
|
6
5
|
onRowClick?: (category: string) => void;
|
|
7
|
-
parentCategory?: DetailedCategory;
|
|
8
6
|
rows: TrendsTableRow[];
|
|
9
7
|
totalRow?: TrendsTableRow;
|
|
10
8
|
}
|
|
11
|
-
export declare const TrendsTable: ({ columns, onRowClick,
|
|
9
|
+
export declare const TrendsTable: ({ columns, onRowClick, rows, totalRow }: TrendsTableProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
10
|
export {};
|
|
@@ -1,37 +1,42 @@
|
|
|
1
|
-
import { CategoryGuidToName as
|
|
2
|
-
import { useEvent as
|
|
3
|
-
import { ANALYTICS_EVENTS as
|
|
4
|
-
import { TRENDS_ANALYTICS_METADATA as
|
|
5
|
-
import { AccessibleSortIcon as
|
|
6
|
-
import { jsx as
|
|
7
|
-
import { useTheme as
|
|
8
|
-
import { DataGridPro as
|
|
9
|
-
var
|
|
10
|
-
const
|
|
11
|
-
|
|
1
|
+
import { CategoryGuidToName as m } from "../../../core/constants/Category.es.js";
|
|
2
|
+
import { useEvent as g } from "../../../common/context/hooks.es.js";
|
|
3
|
+
import { ANALYTICS_EVENTS as b } from "../../../common/constants/Analytics.es.js";
|
|
4
|
+
import { TRENDS_ANALYTICS_METADATA as f } from "../../constants/TrendsWidget.es.js";
|
|
5
|
+
import { AccessibleSortIcon as p } from "./AccessibleSortIcon.es.js";
|
|
6
|
+
import { jsx as T } from "react/jsx-runtime";
|
|
7
|
+
import { useTheme as _ } from "@mui/material/styles";
|
|
8
|
+
import { DataGridPro as y } from "@mui/x-data-grid-pro";
|
|
9
|
+
var t = (e) => e.id !== "total" && !e.isParent, D = ({ columns: e, onRowClick: i, rows: d, totalRow: a }) => {
|
|
10
|
+
const l = _(), { onEvent: u } = g(), n = l.palette.mode === "light", c = !!i, s = (r) => {
|
|
11
|
+
t(r) && (i?.(r.category_guid), u(b.TRENDS_CLICK_LIST_ITEM, {
|
|
12
|
+
...f,
|
|
13
|
+
listItem: m[r.category_guid] ?? r.category,
|
|
14
|
+
destination: r.top_level_category_guid === r.category_guid ? "drill_down" : "drawer"
|
|
15
|
+
}));
|
|
16
|
+
};
|
|
17
|
+
return /* @__PURE__ */ T(y, {
|
|
12
18
|
autoHeight: !0,
|
|
13
|
-
columns:
|
|
19
|
+
columns: e,
|
|
14
20
|
disableColumnFilter: !0,
|
|
15
21
|
disableColumnMenu: !0,
|
|
22
|
+
getRowClassName: (r) => t(r.row) ? "" : "trends-row-static",
|
|
16
23
|
hideFooter: !0,
|
|
17
24
|
initialState: { sorting: { sortModel: [{
|
|
18
25
|
field: "category",
|
|
19
26
|
sort: "asc"
|
|
20
27
|
}] } },
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
...c,
|
|
24
|
-
listItem: l[e.row.category_guid]
|
|
25
|
-
});
|
|
28
|
+
onCellKeyDown: (r, o) => {
|
|
29
|
+
o.key !== "Enter" && o.key !== " " || t(r.row) && (o.preventDefault(), s(r.row));
|
|
26
30
|
},
|
|
27
|
-
|
|
31
|
+
onRowClick: (r) => s(r.row),
|
|
32
|
+
pinnedRows: { bottom: a ? [a] : [] },
|
|
28
33
|
rowHeight: 64,
|
|
29
34
|
rows: d,
|
|
30
35
|
slotProps: { baseIconButton: {
|
|
31
36
|
color: "secondary",
|
|
32
37
|
sx: { ml: 8 }
|
|
33
38
|
} },
|
|
34
|
-
slots: { columnHeaderSortIcon:
|
|
39
|
+
slots: { columnHeaderSortIcon: p },
|
|
35
40
|
sortingOrder: ["asc", "desc"],
|
|
36
41
|
sx: {
|
|
37
42
|
borderColor: "divider",
|
|
@@ -39,15 +44,17 @@ var y = ({ columns: t, onRowClick: i, parentCategory: s, rows: d, totalRow: o })
|
|
|
39
44
|
borderStyle: "solid",
|
|
40
45
|
borderRadius: 2,
|
|
41
46
|
"& .MuiDataGrid-columnHeaders": {
|
|
42
|
-
bgcolor:
|
|
47
|
+
bgcolor: n ? "neutral.50" : "neutral.800",
|
|
43
48
|
borderBottom: "none"
|
|
44
49
|
},
|
|
45
|
-
"& .trends-table-header": { bgcolor:
|
|
50
|
+
"& .trends-table-header": { bgcolor: n ? "neutral.50" : "neutral.800" },
|
|
51
|
+
"& .MuiDataGrid-row": { cursor: c ? "pointer" : "default" },
|
|
52
|
+
"& .trends-row-static": { cursor: "default" }
|
|
46
53
|
}
|
|
47
54
|
});
|
|
48
55
|
};
|
|
49
56
|
export {
|
|
50
|
-
|
|
57
|
+
D as TrendsTable
|
|
51
58
|
};
|
|
52
59
|
|
|
53
60
|
//# sourceMappingURL=TrendsTable.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrendsTable.es.js","names":[],"sources":["../../../../src/trends/components/trendstable/TrendsTable.tsx"],"sourcesContent":["import { ANALYTICS_EVENTS, useEvent } from '../../../common'\nimport { CategoryGuidToName } from '../../../core/constants'\nimport {
|
|
1
|
+
{"version":3,"file":"TrendsTable.es.js","names":[],"sources":["../../../../src/trends/components/trendstable/TrendsTable.tsx"],"sourcesContent":["import { ANALYTICS_EVENTS, useEvent } from '../../../common'\nimport { CategoryGuidToName } from '../../../core/constants'\nimport { TRENDS_ANALYTICS_METADATA } from '../../constants/TrendsWidget'\nimport { DataGridPro, GridColDef } from '@mui/x-data-grid-pro'\nimport { useTheme } from '@mui/material/styles'\nimport { TrendsTableRow } from '../../utils/TrendsTable'\nimport { AccessibleSortIcon } from './AccessibleSortIcon'\n\ninterface TrendsTableProps {\n columns: GridColDef[]\n onRowClick?: (category: string) => void\n rows: TrendsTableRow[]\n totalRow?: TrendsTableRow\n}\n\n// The pinned total row and the synthetic \"General [Parent]\" row (isParent: true,\n// only present in a drilled-in table) are summaries, not navigation destinations.\nconst isRowNavigable = (row: TrendsTableRow) => row.id !== 'total' && !row.isParent\n\nexport const TrendsTable = ({ columns, onRowClick, rows, totalRow }: TrendsTableProps) => {\n const theme = useTheme()\n const { onEvent } = useEvent()\n const isLightMode = theme.palette.mode === 'light'\n const isRowInteractive = Boolean(onRowClick)\n\n // Shared by pointer and keyboard activation; non-navigable rows are dropped so\n // neither path acts on them.\n const activateRow = (row: TrendsTableRow) => {\n if (!isRowNavigable(row)) return\n onRowClick?.(row.category_guid)\n onEvent(ANALYTICS_EVENTS.TRENDS_CLICK_LIST_ITEM, {\n ...TRENDS_ANALYTICS_METADATA,\n // Fall back to the row's display name for user-created categories, which\n // are absent from the static default-category map.\n listItem: CategoryGuidToName[row.category_guid] ?? row.category,\n // A top-level row (guid is its own top level) drills down; a sub-category\n // row opens the drawer.\n destination: row.top_level_category_guid === row.category_guid ? 'drill_down' : 'drawer',\n })\n }\n\n return (\n <DataGridPro\n // autoHeight sizes the grid to its content instead of measuring a flex\n // parent. The parent is an overflow:auto Stack with no fixed height, so the\n // default virtualized layout makes x-data-grid's useGridDimensions loop on\n // its own ResizeObserver measurement (a feedback loop that spikes to React's\n // \"Maximum update depth exceeded\" under a starved CI worker in jsdom). Content\n // sizing removes the measurement feedback and is correct for this unbounded,\n // page-scrolling table.\n autoHeight={true}\n columns={columns}\n disableColumnFilter={true}\n disableColumnMenu={true}\n getRowClassName={(params) => (isRowNavigable(params.row) ? '' : 'trends-row-static')}\n hideFooter={true}\n initialState={{ sorting: { sortModel: [{ field: 'category', sort: 'asc' }] } }}\n onCellKeyDown={(params, event) => {\n // Rows are reachable only by grid cell navigation, so Enter/Space on a\n // cell is the keyboard equivalent of clicking the row. Guard before\n // preventDefault so a non-navigable row keeps the grid's native key\n // handling instead of being swallowed with no action. Space would\n // otherwise scroll the grid.\n if (event.key !== 'Enter' && event.key !== ' ') return\n if (!isRowNavigable(params.row)) return\n event.preventDefault()\n activateRow(params.row)\n }}\n onRowClick={(r) => activateRow(r.row)}\n pinnedRows={{\n bottom: totalRow ? [totalRow] : [],\n }}\n rowHeight={64}\n rows={rows}\n slotProps={{ baseIconButton: { color: 'secondary', sx: { ml: 8 } } }}\n slots={{ columnHeaderSortIcon: AccessibleSortIcon }}\n sortingOrder={['asc', 'desc']}\n sx={{\n borderColor: 'divider',\n borderWidth: 1,\n borderStyle: 'solid',\n borderRadius: 2,\n '& .MuiDataGrid-columnHeaders': {\n bgcolor: isLightMode ? 'neutral.50' : 'neutral.800',\n borderBottom: 'none',\n },\n '& .trends-table-header': {\n bgcolor: isLightMode ? 'neutral.50' : 'neutral.800',\n },\n '& .MuiDataGrid-row': { cursor: isRowInteractive ? 'pointer' : 'default' },\n // getRowClassName stamps trends-row-static on every non-navigable row,\n // including the pinned total row.\n '& .trends-row-static': { cursor: 'default' },\n }}\n />\n )\n}\n"],"mappings":";;;;;;;;AAiBA,IAAM,IAAA,CAAkB,MAAwB,EAAI,OAAO,WAAW,CAAC,EAAI,UAE9D,IAAA,CAAe,EAAE,SAAA,GAAS,YAAA,GAAY,MAAA,GAAM,UAAA,EAAA,MAAiC;AACxF,QAAM,IAAQ,EAAS,GACjB,EAAE,SAAA,EAAA,IAAY,EAAS,GACvB,IAAc,EAAM,QAAQ,SAAS,SACrC,IAAmB,EAAQ,GAI3B,IAAA,CAAe,MAAwB;AAC3C,IAAK,EAAe,CAAG,MACvB,IAAa,EAAI,aAAa,GAC9B,EAAQ,EAAiB,wBAAwB;AAAA,MAC/C,GAAG;AAAA,MAGH,UAAU,EAAmB,EAAI,aAAA,KAAkB,EAAI;AAAA,MAGvD,aAAa,EAAI,4BAA4B,EAAI,gBAAgB,eAAe;AAAA,IAClF,CAAC;AAAA,EACH;AAEA,SACE,gBAAA,EAAC,GAAD;AAAA,IAQE,YAAY;AAAA,IACH,SAAA;AAAA,IACT,qBAAqB;AAAA,IACrB,mBAAmB;AAAA,IACnB,iBAAA,CAAkB,MAAY,EAAe,EAAO,GAAG,IAAI,KAAK;AAAA,IAChE,YAAY;AAAA,IACZ,cAAc,EAAE,SAAS,EAAE,WAAW,CAAC;AAAA,MAAE,OAAO;AAAA,MAAY,MAAM;AAAA,IAAM,CAAC,EAAE,EAAE;AAAA,IAC7E,eAAA,CAAgB,GAAQ,MAAU;AAMhC,MAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,OACtC,EAAe,EAAO,GAAG,MAC9B,EAAM,eAAe,GACrB,EAAY,EAAO,GAAG;AAAA,IACxB;AAAA,IACA,YAAA,CAAa,MAAM,EAAY,EAAE,GAAG;AAAA,IACpC,YAAY,EACV,QAAQ,IAAW,CAAC,CAAQ,IAAI,CAAC,EACnC;AAAA,IACA,WAAW;AAAA,IACL,MAAA;AAAA,IACN,WAAW,EAAE,gBAAgB;AAAA,MAAE,OAAO;AAAA,MAAa,IAAI,EAAE,IAAI,EAAE;AAAA,IAAE,EAAE;AAAA,IACnE,OAAO,EAAE,sBAAsB,EAAmB;AAAA,IAClD,cAAc,CAAC,OAAO,MAAM;AAAA,IAC5B,IAAI;AAAA,MACF,aAAa;AAAA,MACb,aAAa;AAAA,MACb,aAAa;AAAA,MACb,cAAc;AAAA,MACd,gCAAgC;AAAA,QAC9B,SAAS,IAAc,eAAe;AAAA,QACtC,cAAc;AAAA,MAChB;AAAA,MACA,0BAA0B,EACxB,SAAS,IAAc,eAAe,cACxC;AAAA,MACA,sBAAsB,EAAE,QAAQ,IAAmB,YAAY,UAAU;AAAA,MAGzE,wBAAwB,EAAE,QAAQ,UAAU;AAAA,IAC9C;AAAA,EACD,CAAA;AAEL"}
|