@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 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"]}
@@ -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 };
@@ -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
+ }