@mx-cartographer/experiences 9.4.0 → 9.4.1-alpha.al0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/dist/budgets/components/BudgetDetailsDrawer.es.js +133 -95
- package/dist/budgets/components/BudgetDetailsDrawer.es.js.map +1 -1
- package/dist/budgets/components/budgetdetails/AddSubBudget.es.js +64 -63
- package/dist/budgets/components/budgetdetails/AddSubBudget.es.js.map +1 -1
- package/dist/budgets/components/budgetdetails/SubBudgetAmounts.d.ts +10 -0
- package/dist/budgets/components/budgetdetails/SubBudgetAmounts.es.js +58 -0
- package/dist/budgets/components/budgetdetails/SubBudgetAmounts.es.js.map +1 -0
- package/dist/budgets/components/budgetdetails/SubBudgetList.es.js +42 -41
- package/dist/budgets/components/budgetdetails/SubBudgetList.es.js.map +1 -1
- package/dist/budgets/components/budgetlist/BudgetRow.es.js +15 -26
- package/dist/budgets/components/budgetlist/BudgetRow.es.js.map +1 -1
- package/dist/budgets/components/budgetlist/SubBudgetRow.es.js +12 -55
- package/dist/budgets/components/budgetlist/SubBudgetRow.es.js.map +1 -1
- package/dist/budgets/components/shared/BudgetAmount.es.js +14 -15
- package/dist/budgets/components/shared/BudgetAmount.es.js.map +1 -1
- package/dist/budgets/components/shared/BudgetDonut.d.ts +8 -0
- package/dist/budgets/components/shared/BudgetDonut.es.js +25 -0
- package/dist/budgets/components/shared/BudgetDonut.es.js.map +1 -0
- package/dist/budgets/components/shared/SubBudgetCategory.d.ts +7 -0
- package/dist/budgets/components/shared/SubBudgetCategory.es.js +45 -0
- package/dist/budgets/components/shared/SubBudgetCategory.es.js.map +1 -0
- package/dist/core/stores/BudgetsStore.d.ts +3 -1
- package/dist/core/stores/BudgetsStore.es.js +40 -32
- package/dist/core/stores/BudgetsStore.es.js.map +1 -1
- package/dist/core/types/localization/BudgetsCopy.d.ts +3 -0
- package/dist/core/utils/BudgetUtil.es.js +56 -50
- package/dist/core/utils/BudgetUtil.es.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { CategoryGuids as
|
|
2
|
-
import { BudgetsApi as
|
|
3
|
-
import { upsertByGuid as
|
|
4
|
-
import { augmentBudgets as
|
|
5
|
-
import { endOfMonth as
|
|
6
|
-
import { startOfMonth as
|
|
7
|
-
import { startOfToday as
|
|
8
|
-
import { makeAutoObservable as
|
|
9
|
-
var
|
|
10
|
-
api = new
|
|
1
|
+
import { CategoryGuids as u } from "../constants/Category.es.js";
|
|
2
|
+
import { BudgetsApi as n } from "../api/BudgetsApi.es.js";
|
|
3
|
+
import { upsertByGuid as c } from "../utils/upsertByGuid.es.js";
|
|
4
|
+
import { augmentBudgets as h, findBudget as p, findSubBudget as f } from "../utils/BudgetUtil.es.js";
|
|
5
|
+
import { endOfMonth as S } from "date-fns/endOfMonth";
|
|
6
|
+
import { startOfMonth as b } from "date-fns/startOfMonth";
|
|
7
|
+
import { startOfToday as l } from "date-fns/startOfToday";
|
|
8
|
+
import { makeAutoObservable as m, reaction as y, runInAction as i } from "mobx";
|
|
9
|
+
var C = class {
|
|
10
|
+
api = new n("/", "");
|
|
11
11
|
globalStore;
|
|
12
12
|
alert = "";
|
|
13
13
|
dateRange = {
|
|
14
|
-
start:
|
|
15
|
-
end:
|
|
14
|
+
start: b(l()),
|
|
15
|
+
end: S(l())
|
|
16
16
|
};
|
|
17
17
|
budgets = [];
|
|
18
18
|
selectedBudget;
|
|
@@ -21,23 +21,23 @@ var _ = class {
|
|
|
21
21
|
theme = {};
|
|
22
22
|
merchantBudgets = [];
|
|
23
23
|
constructor(t) {
|
|
24
|
-
this.globalStore = t, this.api = new
|
|
24
|
+
this.globalStore = t, this.api = new n(t.endpoint, t.sessionToken, t.onError), m(this);
|
|
25
25
|
}
|
|
26
26
|
loadBudgetData = async () => {
|
|
27
|
-
this.globalStore.recurringTransactionsStore.isDataLoaded || await this.globalStore.recurringTransactionsStore.loadRepeatingTransactionData(), await this.loadBudgets(),
|
|
27
|
+
this.globalStore.recurringTransactionsStore.isDataLoaded || await this.globalStore.recurringTransactionsStore.loadRepeatingTransactionData(), await this.loadBudgets(), y(() => this.detailedBudgets, (t) => {
|
|
28
28
|
if (t) {
|
|
29
29
|
const e = this.selectedBudget;
|
|
30
|
-
e && (this.selectedBudget =
|
|
30
|
+
e && (this.selectedBudget = p(t, e.guid));
|
|
31
31
|
const r = this.selectedSubBudget;
|
|
32
|
-
r && r.parent_guid && (this.selectedSubBudget =
|
|
32
|
+
r && r.parent_guid && (this.selectedSubBudget = f(t, r.guid, r.parent_guid));
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
};
|
|
36
36
|
get detailedBudgets() {
|
|
37
|
-
return
|
|
37
|
+
return h(this.budgets.filter((t) => t.category_guid !== u.INCOME && t.category_guid !== u.TRANSFER && t.category_guid !== u.INVESTMENTS), this.globalStore.categoryStore.detailedCategories, this.globalStore.copyStore.copy.budgets, this.theme).sort((t, e) => t.percentage > e.percentage ? -1 : t.percentage < e.percentage ? 1 : 0);
|
|
38
38
|
}
|
|
39
39
|
get incomeBudget() {
|
|
40
|
-
return
|
|
40
|
+
return h(this.budgets.filter((t) => !t.parent_guid), this.globalStore.categoryStore.detailedCategories, this.globalStore.copyStore.copy.budgets, this.theme).reduce((t, e) => e.category_guid !== u.INCOME ? t : !t || e.percentage > t.percentage ? e : t, void 0)?.amount;
|
|
41
41
|
}
|
|
42
42
|
get unbudgetedCategories() {
|
|
43
43
|
return this.globalStore.categoryStore.spendCategories.filter((t) => !this.detailedBudgets.find((e) => e.category_guid === t.guid));
|
|
@@ -48,8 +48,8 @@ var _ = class {
|
|
|
48
48
|
addBudget = async (t, e, r = null) => {
|
|
49
49
|
try {
|
|
50
50
|
const s = await this.api.addBudget(t, e, r);
|
|
51
|
-
|
|
52
|
-
this.budgets =
|
|
51
|
+
i(() => {
|
|
52
|
+
this.budgets = c(this.budgets, s);
|
|
53
53
|
});
|
|
54
54
|
} catch (s) {
|
|
55
55
|
console.error("Error adding budget:", s), this.globalStore.captureException(s, { store: "BudgetsStore" });
|
|
@@ -57,10 +57,18 @@ var _ = class {
|
|
|
57
57
|
};
|
|
58
58
|
deleteBudget = async (t) => {
|
|
59
59
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const e = this.detailedBudgets.find((d) => d.guid === t), r = /* @__PURE__ */ new Set();
|
|
61
|
+
this.budgets.forEach((d) => {
|
|
62
|
+
d.parent_guid === t && r.add(d.guid);
|
|
63
|
+
}), e?.subBudgets && e.subBudgets.forEach((d) => {
|
|
64
|
+
r.add(d.guid);
|
|
63
65
|
});
|
|
66
|
+
const s = [t, ...Array.from(r)], a = await Promise.allSettled(s.map((d) => this.api.deleteBudget(d))), o = new Set(s.filter((d, B) => a[B].status === "fulfilled"));
|
|
67
|
+
i(() => {
|
|
68
|
+
this.budgets = this.budgets.filter((d) => !o.has(d.guid));
|
|
69
|
+
});
|
|
70
|
+
const g = a.find((d) => d.status === "rejected");
|
|
71
|
+
if (g) throw g.reason;
|
|
64
72
|
} catch (e) {
|
|
65
73
|
console.error("Error deleting budget:", e), this.globalStore.captureException(e, { store: "BudgetsStore" });
|
|
66
74
|
}
|
|
@@ -106,12 +114,12 @@ var _ = class {
|
|
|
106
114
|
updateBudget = async (t) => {
|
|
107
115
|
try {
|
|
108
116
|
const e = await this.api.updateBudget(t);
|
|
109
|
-
|
|
117
|
+
return i(() => {
|
|
110
118
|
const r = this.budgets.findIndex((s) => s.guid === t.guid);
|
|
111
119
|
r >= 0 && (this.budgets[r] = e);
|
|
112
|
-
});
|
|
120
|
+
}), { isSuccess: !0 };
|
|
113
121
|
} catch (e) {
|
|
114
|
-
console.error("Error updating budget:", e), this.globalStore.captureException(e, { store: "BudgetsStore" });
|
|
122
|
+
return console.error("Error updating budget:", e), this.globalStore.captureException(e, { store: "BudgetsStore" }), { isSuccess: !1 };
|
|
115
123
|
}
|
|
116
124
|
};
|
|
117
125
|
getMerchantBudgetByMerchantGuid = (t) => this.merchantBudgets.find((e) => e?.merchant_guid === t);
|
|
@@ -121,8 +129,8 @@ var _ = class {
|
|
|
121
129
|
addMerchantBudget = async (t, e) => {
|
|
122
130
|
try {
|
|
123
131
|
const r = await this.api.addMerchantBudget(t, e);
|
|
124
|
-
return
|
|
125
|
-
this.merchantBudgets =
|
|
132
|
+
return i(() => {
|
|
133
|
+
this.merchantBudgets = c(this.merchantBudgets, r);
|
|
126
134
|
}), { isSuccess: !0 };
|
|
127
135
|
} catch (r) {
|
|
128
136
|
return console.error("Error adding merchant budget:", r), this.globalStore.captureException(r, { store: "BudgetsStore" }), { isSuccess: !1 };
|
|
@@ -131,8 +139,8 @@ var _ = class {
|
|
|
131
139
|
updateMerchantBudget = async (t) => {
|
|
132
140
|
try {
|
|
133
141
|
const { guid: e, amount: r } = t, s = await this.api.updateMerchantBudget(e, r);
|
|
134
|
-
return
|
|
135
|
-
const a = this.merchantBudgets.findIndex((
|
|
142
|
+
return i(() => {
|
|
143
|
+
const a = this.merchantBudgets.findIndex((o) => o.guid === t.guid);
|
|
136
144
|
a >= 0 && (this.merchantBudgets[a] = s);
|
|
137
145
|
}), { isSuccess: !0 };
|
|
138
146
|
} catch (e) {
|
|
@@ -141,7 +149,7 @@ var _ = class {
|
|
|
141
149
|
};
|
|
142
150
|
deleteMerchantBudget = async (t) => {
|
|
143
151
|
try {
|
|
144
|
-
return await this.api.deleteMerchantBudget(t),
|
|
152
|
+
return await this.api.deleteMerchantBudget(t), i(() => {
|
|
145
153
|
this.merchantBudgets = this.merchantBudgets.filter((e) => e.guid !== t);
|
|
146
154
|
}), { isSuccess: !0 };
|
|
147
155
|
} catch (e) {
|
|
@@ -150,7 +158,7 @@ var _ = class {
|
|
|
150
158
|
};
|
|
151
159
|
};
|
|
152
160
|
export {
|
|
153
|
-
|
|
161
|
+
C as BudgetsStore
|
|
154
162
|
};
|
|
155
163
|
|
|
156
164
|
//# sourceMappingURL=BudgetsStore.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BudgetsStore.es.js","names":[],"sources":["../../../src/core/stores/BudgetsStore.ts"],"sourcesContent":["import { makeAutoObservable, reaction, runInAction } from 'mobx'\n\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { startOfMonth } from 'date-fns/startOfMonth'\nimport { startOfToday } from 'date-fns/startOfToday'\n\nimport { Theme } from '@mui/system'\n\nimport { BudgetsApi } from '../api'\nimport { upsertByGuid } from '../utils/upsertByGuid'\nimport { CategoryGuids } from '../constants'\nimport { augmentBudgets, findBudget, findSubBudget } from '../utils/BudgetUtil'\n\nimport { GlobalStore } from './GlobalStore'\n\nimport type {\n Budget,\n Category,\n DateRange,\n DetailedBudget,\n DetailedCategory,\n MerchantBudget,\n} from '../types'\n\nexport class BudgetsStore {\n api: BudgetsApi = new BudgetsApi('/', '')\n\n globalStore: GlobalStore\n\n alert: string = ''\n dateRange: DateRange = { start: startOfMonth(startOfToday()), end: endOfMonth(startOfToday()) }\n budgets: Budget[] = []\n selectedBudget: DetailedBudget | undefined\n selectedSubBudget: DetailedBudget | undefined\n isDataLoaded = false\n theme = {} as Theme\n merchantBudgets: MerchantBudget[] = []\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n this.api = new BudgetsApi(globalStore.endpoint, globalStore.sessionToken, globalStore.onError)\n\n makeAutoObservable(this)\n }\n\n loadBudgetData = async () => {\n if (!this.globalStore.recurringTransactionsStore.isDataLoaded) {\n await this.globalStore.recurringTransactionsStore.loadRepeatingTransactionData()\n }\n await this.loadBudgets()\n\n /** Reaction to update the selected budget/sub-budget if detailedBudgets is updated **/\n reaction(\n () => this.detailedBudgets,\n (detailedBudgets) => {\n if (detailedBudgets) {\n const budget = this.selectedBudget\n if (budget) {\n this.selectedBudget = findBudget(detailedBudgets, budget.guid)\n }\n\n const subBudget = this.selectedSubBudget\n if (subBudget && subBudget.parent_guid) {\n this.selectedSubBudget = findSubBudget(\n detailedBudgets,\n subBudget.guid,\n subBudget.parent_guid,\n )\n }\n }\n },\n )\n }\n\n get detailedBudgets(): DetailedBudget[] {\n return augmentBudgets(\n this.budgets.filter(\n (budget: Budget) =>\n budget.category_guid !== CategoryGuids.INCOME &&\n budget.category_guid !== CategoryGuids.TRANSFER &&\n budget.category_guid !== CategoryGuids.INVESTMENTS,\n ),\n this.globalStore.categoryStore.detailedCategories,\n this.globalStore.copyStore.copy.budgets,\n this.theme,\n ).sort((a, b) => {\n if (a.percentage > b.percentage) return -1\n if (a.percentage < b.percentage) return 1\n return 0\n })\n }\n\n // TODO: Replace this with income from recurring transactions. Income budgets are deprecated.\n get incomeBudget(): number | undefined {\n const filteredBudgets = augmentBudgets(\n this.budgets.filter((budget: Budget) => !budget.parent_guid),\n this.globalStore.categoryStore.detailedCategories,\n this.globalStore.copyStore.copy.budgets,\n this.theme,\n )\n\n const incomeBudget = filteredBudgets.reduce(\n (acc, curr) => {\n if (curr.category_guid !== CategoryGuids.INCOME) return acc\n if (!acc || curr.percentage > acc.percentage) return curr\n return acc\n },\n undefined as DetailedBudget | undefined,\n )\n\n return incomeBudget?.amount\n }\n\n get unbudgetedCategories(): DetailedCategory[] {\n return this.globalStore.categoryStore.spendCategories.filter(\n (category) => !this.detailedBudgets.find((budget) => budget.category_guid === category.guid),\n )\n }\n\n get totalBudgeted(): number {\n return this.detailedBudgets.reduce((total, budget) => total + budget.amount, 0)\n }\n\n addBudget = async (category: Category, amount: number, parentGuid: string | null = null) => {\n try {\n const budget = await this.api.addBudget(category, amount, parentGuid)\n runInAction(() => {\n this.budgets = upsertByGuid(this.budgets, budget)\n })\n } catch (error) {\n console.error('Error adding budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n }\n }\n\n deleteBudget = async (guid: string) => {\n try {\n await this.api.deleteBudget(guid)\n\n runInAction(() => {\n const index = this.budgets.findIndex((budget) => budget.guid === guid)\n if (index >= 0) {\n this.budgets.splice(index, 1)\n }\n })\n } catch (error) {\n console.error('Error deleting budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n }\n }\n\n loadBudgets = async () => {\n try {\n const budgets = await this.api.getBudgets()\n this.setBudgets(budgets)\n } catch (error) {\n console.error('Error occurred while loading budgets:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n throw error\n }\n }\n\n setAlert = (alert: string) => (this.alert = alert)\n\n setBudgets = (budgets: Budget[]) => {\n this.budgets = budgets\n this.isDataLoaded = true\n }\n\n setDateRange = (dateRange: DateRange) => (this.dateRange = dateRange)\n\n setSelectedBudget = (budget: DetailedBudget | undefined) => (this.selectedBudget = budget)\n\n setSelectedSubBudget = (budget: DetailedBudget | undefined) => (this.selectedSubBudget = budget)\n\n setTheme = (theme: Theme) => (this.theme = theme)\n\n /**\n * Recalculate budget by using the average amount from top-level categories\n * @param categories Detailed categories\n */\n recalculateBudgets = async (categories: DetailedCategory[]) => {\n try {\n for (const category of categories) {\n const budget = this.budgets.find((budget) => budget.category_guid === category.guid)\n if (budget) {\n await this.api.updateBudget({\n ...budget,\n amount: category.totalAverageAmount,\n })\n } else {\n await this.api.addBudget(category, category.totalAverageAmount)\n }\n }\n const budgetsToDelete = this.budgets.filter(\n (budget) => !categories.find((c) => c.guid === budget.category_guid),\n )\n\n for (const budgetToDelete of budgetsToDelete) {\n await this.api.deleteBudget(budgetToDelete.guid)\n }\n\n await this.loadBudgets()\n\n return { data: this.budgets, isSuccess: true }\n } catch (error) {\n console.error('Error recalculating budgets:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { data: [], isSuccess: false }\n }\n }\n\n updateBudget = async (budget: Budget) => {\n try {\n const updatedBudget = await this.api.updateBudget(budget)\n\n runInAction(() => {\n const index = this.budgets.findIndex((b) => b.guid === budget.guid)\n if (index >= 0) {\n this.budgets[index] = updatedBudget\n }\n })\n } catch (error) {\n console.error('Error updating budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n }\n }\n\n getMerchantBudgetByMerchantGuid = (merchantGuid: string): MerchantBudget | undefined => {\n return this.merchantBudgets.find(\n (eachMerchantBudget) => eachMerchantBudget?.merchant_guid === merchantGuid,\n )\n }\n\n setMerchantBudgets = (merchantBudgets: MerchantBudget[]) => {\n this.merchantBudgets = merchantBudgets\n }\n\n addMerchantBudget = async (merchantGuid: string, amount: number) => {\n try {\n const newMerchantBudget = await this.api.addMerchantBudget(merchantGuid, amount)\n\n runInAction(() => {\n this.merchantBudgets = upsertByGuid(this.merchantBudgets, newMerchantBudget)\n })\n return { isSuccess: true }\n } catch (error) {\n console.error('Error adding merchant budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { isSuccess: false }\n }\n }\n\n updateMerchantBudget = async (merchantBudget: MerchantBudget) => {\n try {\n const { guid: merchantBudgetGuid, amount } = merchantBudget\n const updatedMerchantBudget = await this.api.updateMerchantBudget(merchantBudgetGuid, amount)\n\n runInAction(() => {\n const index = this.merchantBudgets.findIndex((b) => b.guid === merchantBudget.guid)\n if (index >= 0) {\n this.merchantBudgets[index] = updatedMerchantBudget\n }\n })\n return { isSuccess: true }\n } catch (error) {\n console.error('Error updating merchant budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { isSuccess: false }\n }\n }\n\n deleteMerchantBudget = async (merchantBudgetGuid: string) => {\n try {\n await this.api.deleteMerchantBudget(merchantBudgetGuid)\n\n runInAction(() => {\n this.merchantBudgets = this.merchantBudgets.filter((b) => b.guid !== merchantBudgetGuid)\n })\n return { isSuccess: true }\n } catch (error) {\n console.error('Error deleting merchant budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { isSuccess: false }\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BudgetsStore.es.js","names":[],"sources":["../../../src/core/stores/BudgetsStore.ts"],"sourcesContent":["import { makeAutoObservable, reaction, runInAction } from 'mobx'\n\nimport { endOfMonth } from 'date-fns/endOfMonth'\nimport { startOfMonth } from 'date-fns/startOfMonth'\nimport { startOfToday } from 'date-fns/startOfToday'\n\nimport { Theme } from '@mui/system'\n\nimport { BudgetsApi } from '../api'\nimport { upsertByGuid } from '../utils/upsertByGuid'\nimport { CategoryGuids } from '../constants'\nimport { augmentBudgets, findBudget, findSubBudget } from '../utils/BudgetUtil'\n\nimport { GlobalStore } from './GlobalStore'\n\nimport type {\n Budget,\n Category,\n DateRange,\n DetailedBudget,\n DetailedCategory,\n MerchantBudget,\n} from '../types'\n\nexport class BudgetsStore {\n api: BudgetsApi = new BudgetsApi('/', '')\n\n globalStore: GlobalStore\n\n alert: string = ''\n dateRange: DateRange = { start: startOfMonth(startOfToday()), end: endOfMonth(startOfToday()) }\n budgets: Budget[] = []\n selectedBudget: DetailedBudget | undefined\n selectedSubBudget: DetailedBudget | undefined\n isDataLoaded = false\n theme = {} as Theme\n merchantBudgets: MerchantBudget[] = []\n\n constructor(globalStore: GlobalStore) {\n this.globalStore = globalStore\n this.api = new BudgetsApi(globalStore.endpoint, globalStore.sessionToken, globalStore.onError)\n\n makeAutoObservable(this)\n }\n\n loadBudgetData = async () => {\n if (!this.globalStore.recurringTransactionsStore.isDataLoaded) {\n await this.globalStore.recurringTransactionsStore.loadRepeatingTransactionData()\n }\n await this.loadBudgets()\n\n /** Reaction to update the selected budget/sub-budget if detailedBudgets is updated **/\n reaction(\n () => this.detailedBudgets,\n (detailedBudgets) => {\n if (detailedBudgets) {\n const budget = this.selectedBudget\n if (budget) {\n this.selectedBudget = findBudget(detailedBudgets, budget.guid)\n }\n\n const subBudget = this.selectedSubBudget\n if (subBudget && subBudget.parent_guid) {\n this.selectedSubBudget = findSubBudget(\n detailedBudgets,\n subBudget.guid,\n subBudget.parent_guid,\n )\n }\n }\n },\n )\n }\n\n get detailedBudgets(): DetailedBudget[] {\n return augmentBudgets(\n this.budgets.filter(\n (budget: Budget) =>\n budget.category_guid !== CategoryGuids.INCOME &&\n budget.category_guid !== CategoryGuids.TRANSFER &&\n budget.category_guid !== CategoryGuids.INVESTMENTS,\n ),\n this.globalStore.categoryStore.detailedCategories,\n this.globalStore.copyStore.copy.budgets,\n this.theme,\n ).sort((a, b) => {\n if (a.percentage > b.percentage) return -1\n if (a.percentage < b.percentage) return 1\n return 0\n })\n }\n\n // TODO: Replace this with income from recurring transactions. Income budgets are deprecated.\n get incomeBudget(): number | undefined {\n const filteredBudgets = augmentBudgets(\n this.budgets.filter((budget: Budget) => !budget.parent_guid),\n this.globalStore.categoryStore.detailedCategories,\n this.globalStore.copyStore.copy.budgets,\n this.theme,\n )\n\n const incomeBudget = filteredBudgets.reduce(\n (acc, curr) => {\n if (curr.category_guid !== CategoryGuids.INCOME) return acc\n if (!acc || curr.percentage > acc.percentage) return curr\n return acc\n },\n undefined as DetailedBudget | undefined,\n )\n\n return incomeBudget?.amount\n }\n\n get unbudgetedCategories(): DetailedCategory[] {\n return this.globalStore.categoryStore.spendCategories.filter(\n (category) => !this.detailedBudgets.find((budget) => budget.category_guid === category.guid),\n )\n }\n\n get totalBudgeted(): number {\n return this.detailedBudgets.reduce((total, budget) => total + budget.amount, 0)\n }\n\n addBudget = async (category: Category, amount: number, parentGuid: string | null = null) => {\n try {\n const budget = await this.api.addBudget(category, amount, parentGuid)\n runInAction(() => {\n this.budgets = upsertByGuid(this.budgets, budget)\n })\n } catch (error) {\n console.error('Error adding budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n }\n }\n\n deleteBudget = async (guid: string) => {\n try {\n const detailedParent = this.detailedBudgets.find((b) => b.guid === guid)\n const subBudgetGuids = new Set<string>()\n\n this.budgets.forEach((b) => {\n if (b.parent_guid === guid) {\n subBudgetGuids.add(b.guid)\n }\n })\n\n if (detailedParent?.subBudgets) {\n detailedParent.subBudgets.forEach((sb) => {\n subBudgetGuids.add(sb.guid)\n })\n }\n\n const allGuidsToDelete = [guid, ...Array.from(subBudgetGuids)]\n\n const results = await Promise.allSettled(\n allGuidsToDelete.map((id) => this.api.deleteBudget(id)),\n )\n const deleted = new Set(allGuidsToDelete.filter((_, i) => results[i].status === 'fulfilled'))\n\n runInAction(() => {\n this.budgets = this.budgets.filter((budget) => !deleted.has(budget.guid))\n })\n\n const firstRejection = results.find(\n (r): r is PromiseRejectedResult => r.status === 'rejected',\n )\n if (firstRejection) {\n throw firstRejection.reason\n }\n } catch (error) {\n console.error('Error deleting budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n }\n }\n\n loadBudgets = async () => {\n try {\n const budgets = await this.api.getBudgets()\n this.setBudgets(budgets)\n } catch (error) {\n console.error('Error occurred while loading budgets:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n throw error\n }\n }\n\n setAlert = (alert: string) => (this.alert = alert)\n\n setBudgets = (budgets: Budget[]) => {\n this.budgets = budgets\n this.isDataLoaded = true\n }\n\n setDateRange = (dateRange: DateRange) => (this.dateRange = dateRange)\n\n setSelectedBudget = (budget: DetailedBudget | undefined) => (this.selectedBudget = budget)\n\n setSelectedSubBudget = (budget: DetailedBudget | undefined) => (this.selectedSubBudget = budget)\n\n setTheme = (theme: Theme) => (this.theme = theme)\n\n /**\n * Recalculate budget by using the average amount from top-level categories\n * @param categories Detailed categories\n */\n recalculateBudgets = async (categories: DetailedCategory[]) => {\n try {\n for (const category of categories) {\n const budget = this.budgets.find((budget) => budget.category_guid === category.guid)\n if (budget) {\n await this.api.updateBudget({\n ...budget,\n amount: category.totalAverageAmount,\n })\n } else {\n await this.api.addBudget(category, category.totalAverageAmount)\n }\n }\n const budgetsToDelete = this.budgets.filter(\n (budget) => !categories.find((c) => c.guid === budget.category_guid),\n )\n\n for (const budgetToDelete of budgetsToDelete) {\n await this.api.deleteBudget(budgetToDelete.guid)\n }\n\n await this.loadBudgets()\n\n return { data: this.budgets, isSuccess: true }\n } catch (error) {\n console.error('Error recalculating budgets:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { data: [], isSuccess: false }\n }\n }\n\n updateBudget = async (budget: Budget): Promise<{ isSuccess: boolean }> => {\n try {\n const updatedBudget = await this.api.updateBudget(budget)\n\n runInAction(() => {\n const index = this.budgets.findIndex((b) => b.guid === budget.guid)\n if (index >= 0) {\n this.budgets[index] = updatedBudget\n }\n })\n return { isSuccess: true }\n } catch (error) {\n console.error('Error updating budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { isSuccess: false }\n }\n }\n\n getMerchantBudgetByMerchantGuid = (merchantGuid: string): MerchantBudget | undefined => {\n return this.merchantBudgets.find(\n (eachMerchantBudget) => eachMerchantBudget?.merchant_guid === merchantGuid,\n )\n }\n\n setMerchantBudgets = (merchantBudgets: MerchantBudget[]) => {\n this.merchantBudgets = merchantBudgets\n }\n\n addMerchantBudget = async (merchantGuid: string, amount: number) => {\n try {\n const newMerchantBudget = await this.api.addMerchantBudget(merchantGuid, amount)\n\n runInAction(() => {\n this.merchantBudgets = upsertByGuid(this.merchantBudgets, newMerchantBudget)\n })\n return { isSuccess: true }\n } catch (error) {\n console.error('Error adding merchant budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { isSuccess: false }\n }\n }\n\n updateMerchantBudget = async (merchantBudget: MerchantBudget) => {\n try {\n const { guid: merchantBudgetGuid, amount } = merchantBudget\n const updatedMerchantBudget = await this.api.updateMerchantBudget(merchantBudgetGuid, amount)\n\n runInAction(() => {\n const index = this.merchantBudgets.findIndex((b) => b.guid === merchantBudget.guid)\n if (index >= 0) {\n this.merchantBudgets[index] = updatedMerchantBudget\n }\n })\n return { isSuccess: true }\n } catch (error) {\n console.error('Error updating merchant budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { isSuccess: false }\n }\n }\n\n deleteMerchantBudget = async (merchantBudgetGuid: string) => {\n try {\n await this.api.deleteMerchantBudget(merchantBudgetGuid)\n\n runInAction(() => {\n this.merchantBudgets = this.merchantBudgets.filter((b) => b.guid !== merchantBudgetGuid)\n })\n return { isSuccess: true }\n } catch (error) {\n console.error('Error deleting merchant budget:', error)\n this.globalStore.captureException(error, { store: 'BudgetsStore' })\n return { isSuccess: false }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -40,6 +40,7 @@ export interface BudgetsCopy {
|
|
|
40
40
|
details_delete_button: string;
|
|
41
41
|
details_edit_button: string;
|
|
42
42
|
details_edit_title: string;
|
|
43
|
+
details_edit_title_subbudget: string;
|
|
43
44
|
details_subbudgets_tab: string;
|
|
44
45
|
details_title: string;
|
|
45
46
|
details_transaction_drawer: string;
|
|
@@ -58,6 +59,7 @@ export interface BudgetsCopy {
|
|
|
58
59
|
income_description: string;
|
|
59
60
|
income_title: string;
|
|
60
61
|
left_in_budgets: string;
|
|
62
|
+
monthly_total: string;
|
|
61
63
|
overspent_in_budgets: string;
|
|
62
64
|
overview_title: string;
|
|
63
65
|
percent_spent: string;
|
|
@@ -79,6 +81,7 @@ export interface BudgetsCopy {
|
|
|
79
81
|
status_on_track: string;
|
|
80
82
|
success_alert_delete_merchant_budget: string;
|
|
81
83
|
success_alert_edit_merchant_budget: string;
|
|
84
|
+
your_budget_summary: string;
|
|
82
85
|
zero_state_budget_list_text: string;
|
|
83
86
|
zero_state_description: string;
|
|
84
87
|
zero_state_generate_budgets_header: string;
|
|
@@ -1,71 +1,77 @@
|
|
|
1
|
-
import { buildDynamicCopy as
|
|
2
|
-
import { findDetailedCategory as _, findDetailedSubCategory as
|
|
3
|
-
import { formatCurrency as
|
|
4
|
-
var
|
|
1
|
+
import { buildDynamicCopy as f } from "./Localization.es.js";
|
|
2
|
+
import { findDetailedCategory as _, findDetailedSubCategory as B } from "./CategoryUtil.es.js";
|
|
3
|
+
import { formatCurrency as l } from "./NumberFormatting.es.js";
|
|
4
|
+
var p = (n, t) => !n && !t ? 0 : !n && t ? 101 : t / n * 100, m = (n, t) => {
|
|
5
5
|
const e = {
|
|
6
6
|
background: t.palette.success.main,
|
|
7
|
-
description:
|
|
7
|
+
description: n <= 100 ? t.palette.text.secondary : t.palette.error.main,
|
|
8
8
|
text: t.palette.common.white,
|
|
9
9
|
mercury: t.palette.success.lighter
|
|
10
10
|
};
|
|
11
|
-
return
|
|
12
|
-
}, x = (
|
|
11
|
+
return n > 100 ? (e.background = t.palette.error.main, e.mercury = t.palette.error.lighter) : n > 80 && (e.background = t.palette.warning.main, e.text = t.palette.grey[900], e.mercury = t.palette.warning.lighter), e;
|
|
12
|
+
}, x = (n) => n.map((t) => {
|
|
13
13
|
const e = t.transaction_total;
|
|
14
14
|
return t.amount - e;
|
|
15
|
-
}).reduce((t, e) => t + e, 0),
|
|
16
|
-
const
|
|
17
|
-
for (const
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
...
|
|
23
|
-
budgetColors:
|
|
24
|
-
category:
|
|
25
|
-
description:
|
|
26
|
-
percentage:
|
|
15
|
+
}).reduce((t, e) => t + e, 0), M = (n, t, e, a) => {
|
|
16
|
+
const s = [];
|
|
17
|
+
for (const r of n) {
|
|
18
|
+
const o = _(t, r.category_guid);
|
|
19
|
+
if (!o) continue;
|
|
20
|
+
const i = o.totalAmount, u = p(r.amount, i), g = r.amount - i;
|
|
21
|
+
s.push({
|
|
22
|
+
...r,
|
|
23
|
+
budgetColors: m(u, a),
|
|
24
|
+
category: o,
|
|
25
|
+
description: f(g < 0 ? e.budget_remaining_over : e.budget_remaining_left, l(Math.abs(g), "0,0")),
|
|
26
|
+
percentage: u,
|
|
27
27
|
subBudgets: [],
|
|
28
|
-
transaction_total:
|
|
28
|
+
transaction_total: i
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
-
for (const
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
34
|
-
const
|
|
35
|
-
if (!
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
...
|
|
39
|
-
budgetColors:
|
|
40
|
-
category:
|
|
41
|
-
description:
|
|
42
|
-
percentage:
|
|
31
|
+
for (const r of n) {
|
|
32
|
+
const o = B(t, r.category_guid);
|
|
33
|
+
if (!o) continue;
|
|
34
|
+
const i = o.currentAmount, u = s.find((d) => d.category_guid === o.parent_guid);
|
|
35
|
+
if (!u) continue;
|
|
36
|
+
const g = r.amount - i, c = p(r.amount, i);
|
|
37
|
+
u && u.subBudgets.push({
|
|
38
|
+
...r,
|
|
39
|
+
budgetColors: m(c, a),
|
|
40
|
+
category: o,
|
|
41
|
+
description: f(g < 0 ? e.budget_remaining_over : e.budget_remaining_left, l(Math.abs(g), "0,0")),
|
|
42
|
+
percentage: c,
|
|
43
43
|
subBudgets: [],
|
|
44
|
-
transaction_total:
|
|
44
|
+
transaction_total: i
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
for (const r of s) if (r.subBudgets.length > 0) {
|
|
48
|
+
const o = r.subBudgets.reduce((c, d) => c + d.amount, 0);
|
|
49
|
+
r.amount = o;
|
|
50
|
+
const i = r.transaction_total, u = p(o, i), g = o - i;
|
|
51
|
+
r.percentage = u, r.budgetColors = m(u, a), r.description = f(g < 0 ? e.budget_remaining_over : e.budget_remaining_left, l(Math.abs(g), "0,0"));
|
|
52
|
+
}
|
|
53
|
+
return s;
|
|
54
|
+
}, b = (n, t) => n.find((e) => e.guid === t), w = (n, t, e) => {
|
|
55
|
+
const a = b(n, e);
|
|
56
|
+
if (a)
|
|
57
|
+
return b(a.subBudgets, t);
|
|
58
|
+
}, D = (n, t) => {
|
|
59
|
+
const e = Math.max(0, t - n), a = t > 0 ? Math.min(100, n / t * 100) : 0;
|
|
60
|
+
let s = "success";
|
|
61
|
+
return a >= 100 ? s = "error" : a >= 50 && (s = "warning"), {
|
|
56
62
|
remainingAmount: e,
|
|
57
|
-
spentPercentage:
|
|
58
|
-
iconColor:
|
|
63
|
+
spentPercentage: a,
|
|
64
|
+
iconColor: s
|
|
59
65
|
};
|
|
60
66
|
};
|
|
61
67
|
export {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
M as augmentBudgets,
|
|
69
|
+
b as findBudget,
|
|
70
|
+
w as findSubBudget,
|
|
71
|
+
m as getBudgetColors,
|
|
66
72
|
x as getBudgetDifference,
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
p as getBudgetPercentage,
|
|
74
|
+
D as getRemainingAmountSpentPercentage
|
|
69
75
|
};
|
|
70
76
|
|
|
71
77
|
//# sourceMappingURL=BudgetUtil.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BudgetUtil.es.js","names":[],"sources":["../../../src/core/utils/BudgetUtil.ts"],"sourcesContent":["import type { Theme } from '@mui/system'\n\nimport type { Budget, BudgetColors, BudgetsCopy, DetailedCategory, DetailedBudget } from '../types'\n\nimport { findDetailedCategory, findDetailedSubCategory } from './CategoryUtil'\nimport { formatCurrency } from './NumberFormatting'\nimport { buildDynamicCopy } from './Localization'\n\nexport const getBudgetPercentage = (budgetAmount: number, totalAmount: number) => {\n // No budget amount and nothing spent results in zero percent and green bubble\n if (!budgetAmount && !totalAmount) {\n return 0\n }\n\n // No budget amount and money spent results in 101 percent and red bubble\n if (!budgetAmount && totalAmount) {\n return 101\n }\n\n // Budget amount and money spent results in calculating percentage for color\n return (totalAmount / budgetAmount) * 100\n}\n\nexport const getBudgetColors = (percentage: number, theme: Theme) => {\n const budgetColors: BudgetColors = {\n background: theme.palette.success.main,\n description: percentage <= 100 ? theme.palette.text.secondary : theme.palette.error.main,\n text: theme.palette.common.white,\n mercury: theme.palette.success.lighter,\n }\n\n if (percentage > 100) {\n budgetColors.background = theme.palette.error.main\n budgetColors.mercury = theme.palette.error.lighter\n } else if (percentage > 80) {\n budgetColors.background = theme.palette.warning.main\n budgetColors.text = theme.palette.grey['900']\n budgetColors.mercury = theme.palette.warning.lighter\n }\n\n return budgetColors\n}\n\nexport const getBudgetDifference = (budgets: DetailedBudget[]) => {\n const differences = budgets.map((budget) => {\n const currentAmount = budget.transaction_total\n const totalAmount = budget.amount\n return totalAmount - currentAmount\n })\n\n return differences.reduce((total, difference) => total + difference, 0)\n}\n\nexport const augmentBudgets = (\n budgets: Budget[],\n categories: DetailedCategory[],\n copy: BudgetsCopy,\n theme: Theme,\n): DetailedBudget[] => {\n const detailedBudgets = [] as DetailedBudget[]\n\n // Top-level Categories\n for (const budget of budgets) {\n const category = findDetailedCategory(categories, budget.category_guid)\n if (!category) continue\n\n const spent = category.totalAmount\n\n const percentage = getBudgetPercentage(budget.amount, spent)\n const difference = budget.amount - spent\n\n detailedBudgets.push({\n ...budget,\n budgetColors: getBudgetColors(percentage, theme),\n category,\n description: buildDynamicCopy(\n difference < 0 ? copy.budget_remaining_over : copy.budget_remaining_left,\n formatCurrency(Math.abs(difference), '0,0'),\n ) as string,\n percentage,\n subBudgets: [],\n transaction_total: spent,\n })\n }\n\n // Sub categories\n for (const budget of budgets) {\n const category = findDetailedSubCategory(categories, budget.category_guid)\n if (!category) continue\n\n const spent = category.currentAmount\n\n const parentBudget = detailedBudgets.find((b) => b.category_guid === category.parent_guid)\n if (!parentBudget) continue\n\n const difference = budget.amount - spent\n const percentage = getBudgetPercentage(budget.amount, spent)\n\n if (parentBudget) {\n parentBudget.subBudgets.push({\n ...budget,\n budgetColors: getBudgetColors(percentage, theme),\n category,\n description: buildDynamicCopy(\n difference < 0 ? copy.budget_remaining_over : copy.budget_remaining_left,\n formatCurrency(Math.abs(difference), '0,0'),\n ) as string,\n percentage,\n subBudgets: [],\n transaction_total: spent,\n })\n }\n }\n\n return detailedBudgets\n}\n\nexport const findBudget = (budgets: DetailedBudget[], guid: string): DetailedBudget | undefined => {\n return budgets.find((budget) => budget.guid === guid)\n}\n\nexport const findSubBudget = (\n budgets: DetailedBudget[],\n guid: string,\n parentGuid: string,\n): DetailedBudget | undefined => {\n const parent = findBudget(budgets, parentGuid)\n if (!parent) return undefined\n\n return findBudget(parent.subBudgets, guid)\n}\n\nexport const getRemainingAmountSpentPercentage = (totalTransaction: number, amount: number) => {\n const remainingAmount = Math.max(0, amount - totalTransaction)\n const spentPercentage = amount > 0 ? Math.min(100, (totalTransaction / amount) * 100) : 0\n\n let iconColor = 'success'\n if (spentPercentage >= 100) iconColor = 'error'\n else if (spentPercentage >= 50) iconColor = 'warning'\n\n return {\n remainingAmount,\n spentPercentage,\n iconColor,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BudgetUtil.es.js","names":[],"sources":["../../../src/core/utils/BudgetUtil.ts"],"sourcesContent":["import type { Theme } from '@mui/system'\n\nimport type { Budget, BudgetColors, BudgetsCopy, DetailedCategory, DetailedBudget } from '../types'\n\nimport { findDetailedCategory, findDetailedSubCategory } from './CategoryUtil'\nimport { formatCurrency } from './NumberFormatting'\nimport { buildDynamicCopy } from './Localization'\n\nexport const getBudgetPercentage = (budgetAmount: number, totalAmount: number) => {\n // No budget amount and nothing spent results in zero percent and green bubble\n if (!budgetAmount && !totalAmount) {\n return 0\n }\n\n // No budget amount and money spent results in 101 percent and red bubble\n if (!budgetAmount && totalAmount) {\n return 101\n }\n\n // Budget amount and money spent results in calculating percentage for color\n return (totalAmount / budgetAmount) * 100\n}\n\nexport const getBudgetColors = (percentage: number, theme: Theme) => {\n const budgetColors: BudgetColors = {\n background: theme.palette.success.main,\n description: percentage <= 100 ? theme.palette.text.secondary : theme.palette.error.main,\n text: theme.palette.common.white,\n mercury: theme.palette.success.lighter,\n }\n\n if (percentage > 100) {\n budgetColors.background = theme.palette.error.main\n budgetColors.mercury = theme.palette.error.lighter\n } else if (percentage > 80) {\n budgetColors.background = theme.palette.warning.main\n budgetColors.text = theme.palette.grey['900']\n budgetColors.mercury = theme.palette.warning.lighter\n }\n\n return budgetColors\n}\n\nexport const getBudgetDifference = (budgets: DetailedBudget[]) => {\n const differences = budgets.map((budget) => {\n const currentAmount = budget.transaction_total\n const totalAmount = budget.amount\n return totalAmount - currentAmount\n })\n\n return differences.reduce((total, difference) => total + difference, 0)\n}\n\nexport const augmentBudgets = (\n budgets: Budget[],\n categories: DetailedCategory[],\n copy: BudgetsCopy,\n theme: Theme,\n): DetailedBudget[] => {\n const detailedBudgets = [] as DetailedBudget[]\n\n // Top-level Categories\n for (const budget of budgets) {\n const category = findDetailedCategory(categories, budget.category_guid)\n if (!category) continue\n\n const spent = category.totalAmount\n\n const percentage = getBudgetPercentage(budget.amount, spent)\n const difference = budget.amount - spent\n\n detailedBudgets.push({\n ...budget,\n budgetColors: getBudgetColors(percentage, theme),\n category,\n description: buildDynamicCopy(\n difference < 0 ? copy.budget_remaining_over : copy.budget_remaining_left,\n formatCurrency(Math.abs(difference), '0,0'),\n ) as string,\n percentage,\n subBudgets: [],\n transaction_total: spent,\n })\n }\n\n // Sub categories\n for (const budget of budgets) {\n const category = findDetailedSubCategory(categories, budget.category_guid)\n if (!category) continue\n\n const spent = category.currentAmount\n\n const parentBudget = detailedBudgets.find((b) => b.category_guid === category.parent_guid)\n if (!parentBudget) continue\n\n const difference = budget.amount - spent\n const percentage = getBudgetPercentage(budget.amount, spent)\n\n if (parentBudget) {\n parentBudget.subBudgets.push({\n ...budget,\n budgetColors: getBudgetColors(percentage, theme),\n category,\n description: buildDynamicCopy(\n difference < 0 ? copy.budget_remaining_over : copy.budget_remaining_left,\n formatCurrency(Math.abs(difference), '0,0'),\n ) as string,\n percentage,\n subBudgets: [],\n transaction_total: spent,\n })\n }\n }\n\n for (const parentBudget of detailedBudgets) {\n if (parentBudget.subBudgets.length > 0) {\n const totalSubBudgetAmount = parentBudget.subBudgets.reduce(\n (total, subBudget) => total + subBudget.amount,\n 0,\n )\n parentBudget.amount = totalSubBudgetAmount\n\n const spent = parentBudget.transaction_total\n const percentage = getBudgetPercentage(totalSubBudgetAmount, spent)\n const difference = totalSubBudgetAmount - spent\n\n parentBudget.percentage = percentage\n parentBudget.budgetColors = getBudgetColors(percentage, theme)\n parentBudget.description = buildDynamicCopy(\n difference < 0 ? copy.budget_remaining_over : copy.budget_remaining_left,\n formatCurrency(Math.abs(difference), '0,0'),\n ) as string\n }\n }\n\n return detailedBudgets\n}\n\nexport const findBudget = (budgets: DetailedBudget[], guid: string): DetailedBudget | undefined => {\n return budgets.find((budget) => budget.guid === guid)\n}\n\nexport const findSubBudget = (\n budgets: DetailedBudget[],\n guid: string,\n parentGuid: string,\n): DetailedBudget | undefined => {\n const parent = findBudget(budgets, parentGuid)\n if (!parent) return undefined\n\n return findBudget(parent.subBudgets, guid)\n}\n\nexport const getRemainingAmountSpentPercentage = (totalTransaction: number, amount: number) => {\n const remainingAmount = Math.max(0, amount - totalTransaction)\n const spentPercentage = amount > 0 ? Math.min(100, (totalTransaction / amount) * 100) : 0\n\n let iconColor = 'success'\n if (spentPercentage >= 100) iconColor = 'error'\n else if (spentPercentage >= 50) iconColor = 'warning'\n\n return {\n remainingAmount,\n spentPercentage,\n iconColor,\n }\n}\n"],"mappings":"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"}
|