@pfm-platform/budgets-feature 0.2.0
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/dist/index.cjs +122 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var budgetsDataAccess = require('@pfm-platform/budgets-data-access');
|
|
5
|
+
|
|
6
|
+
// src/hooks/useBudgetProgress.ts
|
|
7
|
+
function useBudgetProgress(options) {
|
|
8
|
+
const { userId, start_date, end_date } = options;
|
|
9
|
+
const { data } = budgetsDataAccess.useBudgets({
|
|
10
|
+
userId,
|
|
11
|
+
filters: { start_date, end_date }
|
|
12
|
+
});
|
|
13
|
+
return react.useMemo(() => {
|
|
14
|
+
if (!data?.budgets || data.budgets.length === 0) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
return data.budgets.map((budget) => {
|
|
18
|
+
const spent = budget.spent || 0;
|
|
19
|
+
const budgetAmount = budget.budget_amount || 0;
|
|
20
|
+
const remaining = budgetAmount - spent;
|
|
21
|
+
const percentSpent = budgetAmount > 0 ? spent / budgetAmount * 100 : 0;
|
|
22
|
+
let state;
|
|
23
|
+
if (spent > budgetAmount) {
|
|
24
|
+
state = "over";
|
|
25
|
+
} else if (spent < budgetAmount) {
|
|
26
|
+
state = "under";
|
|
27
|
+
} else {
|
|
28
|
+
state = "risk";
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
id: budget.id,
|
|
32
|
+
name: budget.name,
|
|
33
|
+
budgetAmount,
|
|
34
|
+
spent,
|
|
35
|
+
remaining,
|
|
36
|
+
percentSpent,
|
|
37
|
+
state
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}, [data]);
|
|
41
|
+
}
|
|
42
|
+
function useBudgetSummary(options) {
|
|
43
|
+
const { userId, start_date, end_date } = options;
|
|
44
|
+
const { data } = budgetsDataAccess.useBudgets({
|
|
45
|
+
userId,
|
|
46
|
+
filters: { start_date, end_date }
|
|
47
|
+
});
|
|
48
|
+
return react.useMemo(() => {
|
|
49
|
+
if (!data?.budgets) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const totalBudget = data.budgets.reduce(
|
|
53
|
+
(sum, budget) => sum + (budget.budget_amount || 0),
|
|
54
|
+
0
|
|
55
|
+
);
|
|
56
|
+
const totalSpent = data.budgets.reduce(
|
|
57
|
+
(sum, budget) => sum + (budget.spent || 0),
|
|
58
|
+
0
|
|
59
|
+
);
|
|
60
|
+
const totalRemaining = totalBudget - totalSpent;
|
|
61
|
+
const percentSpent = totalBudget > 0 ? totalSpent / totalBudget * 100 : 0;
|
|
62
|
+
let state;
|
|
63
|
+
if (totalSpent > totalBudget) {
|
|
64
|
+
state = "over";
|
|
65
|
+
} else if (totalSpent < totalBudget) {
|
|
66
|
+
state = "under";
|
|
67
|
+
} else {
|
|
68
|
+
state = "risk";
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
totalBudgets: data.budgets.length,
|
|
72
|
+
totalBudget,
|
|
73
|
+
totalSpent,
|
|
74
|
+
totalRemaining,
|
|
75
|
+
percentSpent,
|
|
76
|
+
state,
|
|
77
|
+
hasBudgets: data.budgets.length > 0
|
|
78
|
+
};
|
|
79
|
+
}, [data]);
|
|
80
|
+
}
|
|
81
|
+
function useBudgetsByMonth(options) {
|
|
82
|
+
const { userId, start_date, end_date } = options;
|
|
83
|
+
const { data } = budgetsDataAccess.useBudgets({
|
|
84
|
+
userId,
|
|
85
|
+
filters: { start_date, end_date }
|
|
86
|
+
});
|
|
87
|
+
return react.useMemo(() => {
|
|
88
|
+
if (!data?.budgets || data.budgets.length === 0) {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
const grouped = {};
|
|
92
|
+
data.budgets.forEach((budget) => {
|
|
93
|
+
const monthKey = `${budget.year}-${String(budget.month).padStart(2, "0")}`;
|
|
94
|
+
if (!grouped[monthKey]) {
|
|
95
|
+
grouped[monthKey] = {
|
|
96
|
+
totalBudget: 0,
|
|
97
|
+
totalSpent: 0,
|
|
98
|
+
state: "under",
|
|
99
|
+
budgetCount: 0
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const month = grouped[monthKey];
|
|
103
|
+
month.totalBudget += budget.budget_amount || 0;
|
|
104
|
+
month.totalSpent += budget.spent || 0;
|
|
105
|
+
month.budgetCount += 1;
|
|
106
|
+
if (month.totalSpent > month.totalBudget) {
|
|
107
|
+
month.state = "over";
|
|
108
|
+
} else if (month.totalSpent < month.totalBudget) {
|
|
109
|
+
month.state = "under";
|
|
110
|
+
} else {
|
|
111
|
+
month.state = "risk";
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return grouped;
|
|
115
|
+
}, [data]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
exports.useBudgetProgress = useBudgetProgress;
|
|
119
|
+
exports.useBudgetSummary = useBudgetSummary;
|
|
120
|
+
exports.useBudgetsByMonth = useBudgetsByMonth;
|
|
121
|
+
//# sourceMappingURL=index.cjs.map
|
|
122
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useBudgetProgress.ts","../src/hooks/useBudgetSummary.ts","../src/hooks/useBudgetsByMonth.ts"],"names":["useBudgets","useMemo"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useMemo } from 'react';\nimport { useBudgets } from '@pfm-platform/budgets-data-access';\n\nexport interface BudgetProgress {\n id: number;\n name: string;\n budgetAmount: number;\n spent: number;\n remaining: number;\n percentSpent: number;\n state: 'under' | 'risk' | 'over';\n}\n\nexport interface BudgetProgressOptions {\n userId: string;\n start_date?: string;\n end_date?: string;\n}\n\n/**\n * Calculate progress for each budget (spent vs budget amount)\n * Replaces: budgetsStore/List.js listByMonth computed property\n */\nexport function useBudgetProgress(options: BudgetProgressOptions): BudgetProgress[] {\n const { userId, start_date, end_date } = options;\n\n const { data } = useBudgets({\n userId,\n filters: { start_date, end_date },\n });\n\n return useMemo(() => {\n if (!data?.budgets || data.budgets.length === 0) {\n return [];\n }\n\n return data.budgets.map((budget) => {\n const spent = budget.spent || 0;\n const budgetAmount = budget.budget_amount || 0;\n const remaining = budgetAmount - spent;\n const percentSpent = budgetAmount > 0 ? (spent / budgetAmount) * 100 : 0;\n\n let state: 'under' | 'risk' | 'over';\n if (spent > budgetAmount) {\n state = 'over';\n } else if (spent < budgetAmount) {\n state = 'under';\n } else {\n state = 'risk';\n }\n\n return {\n id: budget.id,\n name: budget.name,\n budgetAmount,\n spent,\n remaining,\n percentSpent,\n state,\n };\n });\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport { useBudgets } from '@pfm-platform/budgets-data-access';\n\nexport interface BudgetSummary {\n totalBudgets: number;\n totalBudget: number;\n totalSpent: number;\n totalRemaining: number;\n percentSpent: number;\n state: 'under' | 'risk' | 'over';\n hasBudgets: boolean;\n}\n\nexport interface BudgetSummaryOptions {\n userId: string;\n start_date?: string;\n end_date?: string;\n}\n\n/**\n * Calculate budget summary totals across all budgets\n * Replaces: budgetsStore.meta and budgetsStore.hasBudgets computed properties\n */\nexport function useBudgetSummary(\n options: BudgetSummaryOptions\n): BudgetSummary | null {\n const { userId, start_date, end_date } = options;\n\n const { data } = useBudgets({\n userId,\n filters: { start_date, end_date },\n });\n\n return useMemo(() => {\n if (!data?.budgets) {\n return null;\n }\n\n const totalBudget = data.budgets.reduce(\n (sum, budget) => sum + (budget.budget_amount || 0),\n 0\n );\n const totalSpent = data.budgets.reduce(\n (sum, budget) => sum + (budget.spent || 0),\n 0\n );\n const totalRemaining = totalBudget - totalSpent;\n const percentSpent = totalBudget > 0 ? (totalSpent / totalBudget) * 100 : 0;\n\n let state: 'under' | 'risk' | 'over';\n if (totalSpent > totalBudget) {\n state = 'over';\n } else if (totalSpent < totalBudget) {\n state = 'under';\n } else {\n state = 'risk';\n }\n\n return {\n totalBudgets: data.budgets.length,\n totalBudget,\n totalSpent,\n totalRemaining,\n percentSpent,\n state,\n hasBudgets: data.budgets.length > 0,\n };\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport { useBudgets } from '@pfm-platform/budgets-data-access';\n\nexport interface MonthBudgetSummary {\n totalBudget: number;\n totalSpent: number;\n state: 'under' | 'risk' | 'over';\n budgetCount: number;\n}\n\nexport interface BudgetsByMonthOptions {\n userId: string;\n start_date?: string;\n end_date?: string;\n}\n\n/**\n * Group budgets by month with summary statistics\n * Replaces: budgetsStore/List.js listByMonth computed property\n *\n * Returns a map of month keys (YYYY-MM format) to budget summaries\n */\nexport function useBudgetsByMonth(\n options: BudgetsByMonthOptions\n): Record<string, MonthBudgetSummary> {\n const { userId, start_date, end_date } = options;\n\n const { data } = useBudgets({\n userId,\n filters: { start_date, end_date },\n });\n\n return useMemo(() => {\n if (!data?.budgets || data.budgets.length === 0) {\n return {};\n }\n\n const grouped: Record<string, MonthBudgetSummary> = {};\n\n data.budgets.forEach((budget) => {\n // Create month key in YYYY-MM format\n const monthKey = `${budget.year}-${String(budget.month).padStart(2, '0')}`;\n\n if (!grouped[monthKey]) {\n grouped[monthKey] = {\n totalBudget: 0,\n totalSpent: 0,\n state: 'under',\n budgetCount: 0,\n };\n }\n\n const month = grouped[monthKey];\n month.totalBudget += budget.budget_amount || 0;\n month.totalSpent += budget.spent || 0;\n month.budgetCount += 1;\n\n // Determine state\n if (month.totalSpent > month.totalBudget) {\n month.state = 'over';\n } else if (month.totalSpent < month.totalBudget) {\n month.state = 'under';\n } else {\n month.state = 'risk';\n }\n });\n\n return grouped;\n }, [data]);\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
interface BudgetProgress {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
budgetAmount: number;
|
|
5
|
+
spent: number;
|
|
6
|
+
remaining: number;
|
|
7
|
+
percentSpent: number;
|
|
8
|
+
state: 'under' | 'risk' | 'over';
|
|
9
|
+
}
|
|
10
|
+
interface BudgetProgressOptions {
|
|
11
|
+
userId: string;
|
|
12
|
+
start_date?: string;
|
|
13
|
+
end_date?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Calculate progress for each budget (spent vs budget amount)
|
|
17
|
+
* Replaces: budgetsStore/List.js listByMonth computed property
|
|
18
|
+
*/
|
|
19
|
+
declare function useBudgetProgress(options: BudgetProgressOptions): BudgetProgress[];
|
|
20
|
+
|
|
21
|
+
interface BudgetSummary {
|
|
22
|
+
totalBudgets: number;
|
|
23
|
+
totalBudget: number;
|
|
24
|
+
totalSpent: number;
|
|
25
|
+
totalRemaining: number;
|
|
26
|
+
percentSpent: number;
|
|
27
|
+
state: 'under' | 'risk' | 'over';
|
|
28
|
+
hasBudgets: boolean;
|
|
29
|
+
}
|
|
30
|
+
interface BudgetSummaryOptions {
|
|
31
|
+
userId: string;
|
|
32
|
+
start_date?: string;
|
|
33
|
+
end_date?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Calculate budget summary totals across all budgets
|
|
37
|
+
* Replaces: budgetsStore.meta and budgetsStore.hasBudgets computed properties
|
|
38
|
+
*/
|
|
39
|
+
declare function useBudgetSummary(options: BudgetSummaryOptions): BudgetSummary | null;
|
|
40
|
+
|
|
41
|
+
interface MonthBudgetSummary {
|
|
42
|
+
totalBudget: number;
|
|
43
|
+
totalSpent: number;
|
|
44
|
+
state: 'under' | 'risk' | 'over';
|
|
45
|
+
budgetCount: number;
|
|
46
|
+
}
|
|
47
|
+
interface BudgetsByMonthOptions {
|
|
48
|
+
userId: string;
|
|
49
|
+
start_date?: string;
|
|
50
|
+
end_date?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Group budgets by month with summary statistics
|
|
54
|
+
* Replaces: budgetsStore/List.js listByMonth computed property
|
|
55
|
+
*
|
|
56
|
+
* Returns a map of month keys (YYYY-MM format) to budget summaries
|
|
57
|
+
*/
|
|
58
|
+
declare function useBudgetsByMonth(options: BudgetsByMonthOptions): Record<string, MonthBudgetSummary>;
|
|
59
|
+
|
|
60
|
+
export { type BudgetProgress, type BudgetProgressOptions, type BudgetSummary, type BudgetSummaryOptions, type BudgetsByMonthOptions, type MonthBudgetSummary, useBudgetProgress, useBudgetSummary, useBudgetsByMonth };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
interface BudgetProgress {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
budgetAmount: number;
|
|
5
|
+
spent: number;
|
|
6
|
+
remaining: number;
|
|
7
|
+
percentSpent: number;
|
|
8
|
+
state: 'under' | 'risk' | 'over';
|
|
9
|
+
}
|
|
10
|
+
interface BudgetProgressOptions {
|
|
11
|
+
userId: string;
|
|
12
|
+
start_date?: string;
|
|
13
|
+
end_date?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Calculate progress for each budget (spent vs budget amount)
|
|
17
|
+
* Replaces: budgetsStore/List.js listByMonth computed property
|
|
18
|
+
*/
|
|
19
|
+
declare function useBudgetProgress(options: BudgetProgressOptions): BudgetProgress[];
|
|
20
|
+
|
|
21
|
+
interface BudgetSummary {
|
|
22
|
+
totalBudgets: number;
|
|
23
|
+
totalBudget: number;
|
|
24
|
+
totalSpent: number;
|
|
25
|
+
totalRemaining: number;
|
|
26
|
+
percentSpent: number;
|
|
27
|
+
state: 'under' | 'risk' | 'over';
|
|
28
|
+
hasBudgets: boolean;
|
|
29
|
+
}
|
|
30
|
+
interface BudgetSummaryOptions {
|
|
31
|
+
userId: string;
|
|
32
|
+
start_date?: string;
|
|
33
|
+
end_date?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Calculate budget summary totals across all budgets
|
|
37
|
+
* Replaces: budgetsStore.meta and budgetsStore.hasBudgets computed properties
|
|
38
|
+
*/
|
|
39
|
+
declare function useBudgetSummary(options: BudgetSummaryOptions): BudgetSummary | null;
|
|
40
|
+
|
|
41
|
+
interface MonthBudgetSummary {
|
|
42
|
+
totalBudget: number;
|
|
43
|
+
totalSpent: number;
|
|
44
|
+
state: 'under' | 'risk' | 'over';
|
|
45
|
+
budgetCount: number;
|
|
46
|
+
}
|
|
47
|
+
interface BudgetsByMonthOptions {
|
|
48
|
+
userId: string;
|
|
49
|
+
start_date?: string;
|
|
50
|
+
end_date?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Group budgets by month with summary statistics
|
|
54
|
+
* Replaces: budgetsStore/List.js listByMonth computed property
|
|
55
|
+
*
|
|
56
|
+
* Returns a map of month keys (YYYY-MM format) to budget summaries
|
|
57
|
+
*/
|
|
58
|
+
declare function useBudgetsByMonth(options: BudgetsByMonthOptions): Record<string, MonthBudgetSummary>;
|
|
59
|
+
|
|
60
|
+
export { type BudgetProgress, type BudgetProgressOptions, type BudgetSummary, type BudgetSummaryOptions, type BudgetsByMonthOptions, type MonthBudgetSummary, useBudgetProgress, useBudgetSummary, useBudgetsByMonth };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useBudgets } from '@pfm-platform/budgets-data-access';
|
|
3
|
+
|
|
4
|
+
// src/hooks/useBudgetProgress.ts
|
|
5
|
+
function useBudgetProgress(options) {
|
|
6
|
+
const { userId, start_date, end_date } = options;
|
|
7
|
+
const { data } = useBudgets({
|
|
8
|
+
userId,
|
|
9
|
+
filters: { start_date, end_date }
|
|
10
|
+
});
|
|
11
|
+
return useMemo(() => {
|
|
12
|
+
if (!data?.budgets || data.budgets.length === 0) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
return data.budgets.map((budget) => {
|
|
16
|
+
const spent = budget.spent || 0;
|
|
17
|
+
const budgetAmount = budget.budget_amount || 0;
|
|
18
|
+
const remaining = budgetAmount - spent;
|
|
19
|
+
const percentSpent = budgetAmount > 0 ? spent / budgetAmount * 100 : 0;
|
|
20
|
+
let state;
|
|
21
|
+
if (spent > budgetAmount) {
|
|
22
|
+
state = "over";
|
|
23
|
+
} else if (spent < budgetAmount) {
|
|
24
|
+
state = "under";
|
|
25
|
+
} else {
|
|
26
|
+
state = "risk";
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
id: budget.id,
|
|
30
|
+
name: budget.name,
|
|
31
|
+
budgetAmount,
|
|
32
|
+
spent,
|
|
33
|
+
remaining,
|
|
34
|
+
percentSpent,
|
|
35
|
+
state
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}, [data]);
|
|
39
|
+
}
|
|
40
|
+
function useBudgetSummary(options) {
|
|
41
|
+
const { userId, start_date, end_date } = options;
|
|
42
|
+
const { data } = useBudgets({
|
|
43
|
+
userId,
|
|
44
|
+
filters: { start_date, end_date }
|
|
45
|
+
});
|
|
46
|
+
return useMemo(() => {
|
|
47
|
+
if (!data?.budgets) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const totalBudget = data.budgets.reduce(
|
|
51
|
+
(sum, budget) => sum + (budget.budget_amount || 0),
|
|
52
|
+
0
|
|
53
|
+
);
|
|
54
|
+
const totalSpent = data.budgets.reduce(
|
|
55
|
+
(sum, budget) => sum + (budget.spent || 0),
|
|
56
|
+
0
|
|
57
|
+
);
|
|
58
|
+
const totalRemaining = totalBudget - totalSpent;
|
|
59
|
+
const percentSpent = totalBudget > 0 ? totalSpent / totalBudget * 100 : 0;
|
|
60
|
+
let state;
|
|
61
|
+
if (totalSpent > totalBudget) {
|
|
62
|
+
state = "over";
|
|
63
|
+
} else if (totalSpent < totalBudget) {
|
|
64
|
+
state = "under";
|
|
65
|
+
} else {
|
|
66
|
+
state = "risk";
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
totalBudgets: data.budgets.length,
|
|
70
|
+
totalBudget,
|
|
71
|
+
totalSpent,
|
|
72
|
+
totalRemaining,
|
|
73
|
+
percentSpent,
|
|
74
|
+
state,
|
|
75
|
+
hasBudgets: data.budgets.length > 0
|
|
76
|
+
};
|
|
77
|
+
}, [data]);
|
|
78
|
+
}
|
|
79
|
+
function useBudgetsByMonth(options) {
|
|
80
|
+
const { userId, start_date, end_date } = options;
|
|
81
|
+
const { data } = useBudgets({
|
|
82
|
+
userId,
|
|
83
|
+
filters: { start_date, end_date }
|
|
84
|
+
});
|
|
85
|
+
return useMemo(() => {
|
|
86
|
+
if (!data?.budgets || data.budgets.length === 0) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
const grouped = {};
|
|
90
|
+
data.budgets.forEach((budget) => {
|
|
91
|
+
const monthKey = `${budget.year}-${String(budget.month).padStart(2, "0")}`;
|
|
92
|
+
if (!grouped[monthKey]) {
|
|
93
|
+
grouped[monthKey] = {
|
|
94
|
+
totalBudget: 0,
|
|
95
|
+
totalSpent: 0,
|
|
96
|
+
state: "under",
|
|
97
|
+
budgetCount: 0
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const month = grouped[monthKey];
|
|
101
|
+
month.totalBudget += budget.budget_amount || 0;
|
|
102
|
+
month.totalSpent += budget.spent || 0;
|
|
103
|
+
month.budgetCount += 1;
|
|
104
|
+
if (month.totalSpent > month.totalBudget) {
|
|
105
|
+
month.state = "over";
|
|
106
|
+
} else if (month.totalSpent < month.totalBudget) {
|
|
107
|
+
month.state = "under";
|
|
108
|
+
} else {
|
|
109
|
+
month.state = "risk";
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return grouped;
|
|
113
|
+
}, [data]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { useBudgetProgress, useBudgetSummary, useBudgetsByMonth };
|
|
117
|
+
//# sourceMappingURL=index.js.map
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useBudgetProgress.ts","../src/hooks/useBudgetSummary.ts","../src/hooks/useBudgetsByMonth.ts"],"names":["useBudgets","useMemo"],"mappings":"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","file":"index.js","sourcesContent":["import { useMemo } from 'react';\nimport { useBudgets } from '@pfm-platform/budgets-data-access';\n\nexport interface BudgetProgress {\n id: number;\n name: string;\n budgetAmount: number;\n spent: number;\n remaining: number;\n percentSpent: number;\n state: 'under' | 'risk' | 'over';\n}\n\nexport interface BudgetProgressOptions {\n userId: string;\n start_date?: string;\n end_date?: string;\n}\n\n/**\n * Calculate progress for each budget (spent vs budget amount)\n * Replaces: budgetsStore/List.js listByMonth computed property\n */\nexport function useBudgetProgress(options: BudgetProgressOptions): BudgetProgress[] {\n const { userId, start_date, end_date } = options;\n\n const { data } = useBudgets({\n userId,\n filters: { start_date, end_date },\n });\n\n return useMemo(() => {\n if (!data?.budgets || data.budgets.length === 0) {\n return [];\n }\n\n return data.budgets.map((budget) => {\n const spent = budget.spent || 0;\n const budgetAmount = budget.budget_amount || 0;\n const remaining = budgetAmount - spent;\n const percentSpent = budgetAmount > 0 ? (spent / budgetAmount) * 100 : 0;\n\n let state: 'under' | 'risk' | 'over';\n if (spent > budgetAmount) {\n state = 'over';\n } else if (spent < budgetAmount) {\n state = 'under';\n } else {\n state = 'risk';\n }\n\n return {\n id: budget.id,\n name: budget.name,\n budgetAmount,\n spent,\n remaining,\n percentSpent,\n state,\n };\n });\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport { useBudgets } from '@pfm-platform/budgets-data-access';\n\nexport interface BudgetSummary {\n totalBudgets: number;\n totalBudget: number;\n totalSpent: number;\n totalRemaining: number;\n percentSpent: number;\n state: 'under' | 'risk' | 'over';\n hasBudgets: boolean;\n}\n\nexport interface BudgetSummaryOptions {\n userId: string;\n start_date?: string;\n end_date?: string;\n}\n\n/**\n * Calculate budget summary totals across all budgets\n * Replaces: budgetsStore.meta and budgetsStore.hasBudgets computed properties\n */\nexport function useBudgetSummary(\n options: BudgetSummaryOptions\n): BudgetSummary | null {\n const { userId, start_date, end_date } = options;\n\n const { data } = useBudgets({\n userId,\n filters: { start_date, end_date },\n });\n\n return useMemo(() => {\n if (!data?.budgets) {\n return null;\n }\n\n const totalBudget = data.budgets.reduce(\n (sum, budget) => sum + (budget.budget_amount || 0),\n 0\n );\n const totalSpent = data.budgets.reduce(\n (sum, budget) => sum + (budget.spent || 0),\n 0\n );\n const totalRemaining = totalBudget - totalSpent;\n const percentSpent = totalBudget > 0 ? (totalSpent / totalBudget) * 100 : 0;\n\n let state: 'under' | 'risk' | 'over';\n if (totalSpent > totalBudget) {\n state = 'over';\n } else if (totalSpent < totalBudget) {\n state = 'under';\n } else {\n state = 'risk';\n }\n\n return {\n totalBudgets: data.budgets.length,\n totalBudget,\n totalSpent,\n totalRemaining,\n percentSpent,\n state,\n hasBudgets: data.budgets.length > 0,\n };\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport { useBudgets } from '@pfm-platform/budgets-data-access';\n\nexport interface MonthBudgetSummary {\n totalBudget: number;\n totalSpent: number;\n state: 'under' | 'risk' | 'over';\n budgetCount: number;\n}\n\nexport interface BudgetsByMonthOptions {\n userId: string;\n start_date?: string;\n end_date?: string;\n}\n\n/**\n * Group budgets by month with summary statistics\n * Replaces: budgetsStore/List.js listByMonth computed property\n *\n * Returns a map of month keys (YYYY-MM format) to budget summaries\n */\nexport function useBudgetsByMonth(\n options: BudgetsByMonthOptions\n): Record<string, MonthBudgetSummary> {\n const { userId, start_date, end_date } = options;\n\n const { data } = useBudgets({\n userId,\n filters: { start_date, end_date },\n });\n\n return useMemo(() => {\n if (!data?.budgets || data.budgets.length === 0) {\n return {};\n }\n\n const grouped: Record<string, MonthBudgetSummary> = {};\n\n data.budgets.forEach((budget) => {\n // Create month key in YYYY-MM format\n const monthKey = `${budget.year}-${String(budget.month).padStart(2, '0')}`;\n\n if (!grouped[monthKey]) {\n grouped[monthKey] = {\n totalBudget: 0,\n totalSpent: 0,\n state: 'under',\n budgetCount: 0,\n };\n }\n\n const month = grouped[monthKey];\n month.totalBudget += budget.budget_amount || 0;\n month.totalSpent += budget.spent || 0;\n month.budgetCount += 1;\n\n // Determine state\n if (month.totalSpent > month.totalBudget) {\n month.state = 'over';\n } else if (month.totalSpent < month.totalBudget) {\n month.state = 'under';\n } else {\n month.state = 'risk';\n }\n });\n\n return grouped;\n }, [data]);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pfm-platform/budgets-feature",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"react": "19.2.0",
|
|
9
|
+
"@pfm-platform/budgets-data-access": "0.2.0",
|
|
10
|
+
"@pfm-platform/shared": "0.1.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@tanstack/react-query": "5.90.9",
|
|
14
|
+
"@testing-library/react": "^16.3.0",
|
|
15
|
+
"@testing-library/user-event": "^14.6.1",
|
|
16
|
+
"@types/react": "^19.2.5",
|
|
17
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
18
|
+
"@vitest/coverage-v8": "^4.0.9",
|
|
19
|
+
"jsdom": "^27.2.0",
|
|
20
|
+
"react-dom": "19.2.0",
|
|
21
|
+
"typescript": "5.9.3",
|
|
22
|
+
"vitest": "4.0.9"
|
|
23
|
+
},
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"description": "Personal Finance Management - BUDGETS feature layer",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"pfm",
|
|
39
|
+
"finance",
|
|
40
|
+
"budgets",
|
|
41
|
+
"feature",
|
|
42
|
+
"react",
|
|
43
|
+
"typescript"
|
|
44
|
+
],
|
|
45
|
+
"author": "Lenny Miller",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/lennylmiller/pfm-research",
|
|
50
|
+
"directory": "packages/budgets/feature"
|
|
51
|
+
},
|
|
52
|
+
"bugs": "https://github.com/lennylmiller/pfm-research/issues",
|
|
53
|
+
"homepage": "https://github.com/lennylmiller/pfm-research#readme",
|
|
54
|
+
"scripts": {
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"test:ui": "vitest --ui",
|
|
58
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean"
|
|
59
|
+
}
|
|
60
|
+
}
|