@pfm-platform/accounts-feature 0.1.1

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,213 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var accountsDataAccess = require('@pfm-platform/accounts-data-access');
5
+
6
+ // src/hooks/useAccountTypes.ts
7
+
8
+ // src/constants.ts
9
+ var ACCOUNT_TYPE_NAMES = {
10
+ checking: "Checking",
11
+ savings: "Savings",
12
+ cards: "Card",
13
+ student_loans: "Student Loan",
14
+ bill: "Bill",
15
+ autos: "Auto",
16
+ home: "Mortgage",
17
+ investment: "Investment",
18
+ loan: "Loan",
19
+ asset: "Asset",
20
+ cd: "Certificate"
21
+ };
22
+ var GROUPED_ACCOUNT_TYPES = [
23
+ {
24
+ name: "Cash",
25
+ types: ["checking", "savings", "money_market"]
26
+ },
27
+ {
28
+ name: "Credit Cards",
29
+ types: ["cards"]
30
+ },
31
+ {
32
+ name: "Debts",
33
+ types: ["autos", "home", "loan", "student_loans", "creditline"]
34
+ },
35
+ {
36
+ name: "Investments",
37
+ types: ["investment"]
38
+ },
39
+ {
40
+ name: "Assets",
41
+ types: ["asset", "cd"]
42
+ },
43
+ {
44
+ name: "Bills",
45
+ types: ["bill"]
46
+ }
47
+ ];
48
+ var USER_ERROR_CODES = [
49
+ "201",
50
+ "203",
51
+ "204",
52
+ "209",
53
+ "300",
54
+ "301",
55
+ "302",
56
+ "303",
57
+ "304",
58
+ "305",
59
+ "306",
60
+ "307",
61
+ "701",
62
+ "103",
63
+ "108",
64
+ "109",
65
+ "185",
66
+ "187",
67
+ "913",
68
+ "914",
69
+ "931",
70
+ "936"
71
+ ];
72
+
73
+ // src/utils/groupBy.ts
74
+ function groupBy(array, key) {
75
+ return array.reduce(
76
+ (result, item) => {
77
+ const groupKey = String(item[key]);
78
+ if (!result[groupKey]) {
79
+ result[groupKey] = [];
80
+ }
81
+ result[groupKey].push(item);
82
+ return result;
83
+ },
84
+ {}
85
+ );
86
+ }
87
+
88
+ // src/hooks/useAccountTypes.ts
89
+ function useAccountTypes(userId) {
90
+ const { data } = accountsDataAccess.useAccounts({ userId });
91
+ return react.useMemo(() => {
92
+ if (!data?.accounts || data.accounts.length === 0) {
93
+ return [];
94
+ }
95
+ const mappedByType = groupBy(data.accounts, "display_account_type");
96
+ return GROUPED_ACCOUNT_TYPES.map((grouped) => ({
97
+ name: grouped.name,
98
+ accounts: grouped.types.flatMap((type) => mappedByType[type] || [])
99
+ })).filter((group) => group.accounts.length > 0).map((accountType) => ({
100
+ ...accountType,
101
+ sum: accountType.accounts.reduce((sum, account) => sum + (parseFloat(account.balance) || 0), 0)
102
+ }));
103
+ }, [data]);
104
+ }
105
+ function useAccountFilters(userId) {
106
+ const { data } = accountsDataAccess.useAccounts({ userId });
107
+ return react.useMemo(() => {
108
+ if (!data?.accounts || data.accounts.length === 0) {
109
+ return { opened: [], closed: [] };
110
+ }
111
+ return {
112
+ opened: data.accounts.filter(
113
+ (account) => account.state === "active" && account.include_in_dashboard
114
+ ),
115
+ closed: data.accounts.filter(
116
+ (account) => account.state === "closed" || account.state === "archived" || !account.include_in_dashboard
117
+ )
118
+ };
119
+ }, [data]);
120
+ }
121
+ function useAccountSummary(userId) {
122
+ const { data } = accountsDataAccess.useAccounts({ userId });
123
+ const { opened, closed } = useAccountFilters(userId);
124
+ return react.useMemo(() => {
125
+ if (!data?.accounts) {
126
+ return null;
127
+ }
128
+ return {
129
+ totalAccounts: data.accounts.length,
130
+ openedCount: opened.length,
131
+ closedCount: closed.length,
132
+ totalBalance: data.accounts.reduce((sum, account) => sum + (parseFloat(account.balance) || 0), 0),
133
+ hasAccounts: data.accounts.length > 0
134
+ };
135
+ }, [data, opened, closed]);
136
+ }
137
+ function useNetWorthSummary(userId) {
138
+ const { data, isLoading } = accountsDataAccess.useNetWorth({ userId });
139
+ return react.useMemo(() => {
140
+ if (!data || isLoading) {
141
+ return null;
142
+ }
143
+ const netWorth = parseFloat(data.meta.net_worth);
144
+ const netWorthChange = parseFloat(data.meta.net_worth_change);
145
+ const totalAssets = parseFloat(data.meta.total_assets);
146
+ const totalDebts = parseFloat(data.meta.total_debts);
147
+ const manualAssets = data.assets.filter((a) => a.additional_networth_account);
148
+ const aggregatedAssets = data.assets.filter((a) => !a.additional_networth_account);
149
+ const manualDebts = data.debts.filter((d) => d.additional_networth_account);
150
+ const aggregatedDebts = data.debts.filter((d) => !d.additional_networth_account);
151
+ return {
152
+ netWorth,
153
+ netWorthChange,
154
+ totalAssets,
155
+ totalDebts,
156
+ assetCount: data.assets.length,
157
+ debtCount: data.debts.length,
158
+ manualAssetCount: manualAssets.length,
159
+ manualDebtCount: manualDebts.length,
160
+ aggregatedAssetCount: aggregatedAssets.length,
161
+ aggregatedDebtCount: aggregatedDebts.length,
162
+ hasAssets: data.assets.length > 0,
163
+ hasDebts: data.debts.length > 0,
164
+ hasManualAccounts: manualAssets.length > 0 || manualDebts.length > 0,
165
+ isPositiveNetWorth: netWorth >= 0,
166
+ isIncreasing: netWorthChange > 0
167
+ };
168
+ }, [data, isLoading]);
169
+ }
170
+ function useNetWorthHistory(userId) {
171
+ const { data, isLoading } = accountsDataAccess.useNetWorth({ userId });
172
+ return react.useMemo(() => {
173
+ if (!data || isLoading || !data.networth_histories) {
174
+ return null;
175
+ }
176
+ return data.networth_histories.map((history) => {
177
+ const totalAsNumber = parseFloat(history.total);
178
+ const totalAssetAsNumber = parseFloat(history.total_asset);
179
+ const totalDebtAsNumber = parseFloat(history.total_debt);
180
+ const sinceLastMonthAsNumber = parseFloat(history.since_last_month);
181
+ const year = parseInt(history.year, 10);
182
+ const month = parseInt(history.month, 10) - 1;
183
+ const date = new Date(year, month, 1);
184
+ const formattedDate = date.toLocaleDateString("en-US", {
185
+ month: "short",
186
+ year: "numeric"
187
+ });
188
+ return {
189
+ ...history,
190
+ totalAsNumber,
191
+ totalAssetAsNumber,
192
+ totalDebtAsNumber,
193
+ sinceLastMonthAsNumber,
194
+ date,
195
+ formattedDate,
196
+ isPositive: totalAsNumber >= 0,
197
+ isGrowing: sinceLastMonthAsNumber > 0
198
+ };
199
+ }).sort((a, b) => a.date.getTime() - b.date.getTime());
200
+ }, [data, isLoading]);
201
+ }
202
+
203
+ exports.ACCOUNT_TYPE_NAMES = ACCOUNT_TYPE_NAMES;
204
+ exports.GROUPED_ACCOUNT_TYPES = GROUPED_ACCOUNT_TYPES;
205
+ exports.USER_ERROR_CODES = USER_ERROR_CODES;
206
+ exports.groupBy = groupBy;
207
+ exports.useAccountFilters = useAccountFilters;
208
+ exports.useAccountSummary = useAccountSummary;
209
+ exports.useAccountTypes = useAccountTypes;
210
+ exports.useNetWorthHistory = useNetWorthHistory;
211
+ exports.useNetWorthSummary = useNetWorthSummary;
212
+ //# sourceMappingURL=index.cjs.map
213
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils/groupBy.ts","../src/hooks/useAccountTypes.ts","../src/hooks/useAccountFilters.ts","../src/hooks/useAccountSummary.ts","../src/hooks/useNetWorthSummary.ts","../src/hooks/useNetWorthHistory.ts"],"names":["useAccounts","useMemo","useNetWorth"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Account type display names\n * Maps account_type to human-readable names\n */\nexport const ACCOUNT_TYPE_NAMES = {\n checking: 'Checking',\n savings: 'Savings',\n cards: 'Card',\n student_loans: 'Student Loan',\n bill: 'Bill',\n autos: 'Auto',\n home: 'Mortgage',\n investment: 'Investment',\n loan: 'Loan',\n asset: 'Asset',\n cd: 'Certificate',\n} as const;\n\n/**\n * Grouped account types for categorization\n * Used by useAccountTypes to organize accounts into logical groups\n */\nexport const GROUPED_ACCOUNT_TYPES = [\n {\n name: 'Cash',\n types: ['checking', 'savings', 'money_market'],\n },\n {\n name: 'Credit Cards',\n types: ['cards'],\n },\n {\n name: 'Debts',\n types: ['autos', 'home', 'loan', 'student_loans', 'creditline'],\n },\n {\n name: 'Investments',\n types: ['investment'],\n },\n {\n name: 'Assets',\n types: ['asset', 'cd'],\n },\n {\n name: 'Bills',\n types: ['bill'],\n },\n] as const;\n\n/**\n * User error codes that indicate credentials need updating\n */\nexport const USER_ERROR_CODES = [\n '201',\n '203',\n '204',\n '209',\n '300',\n '301',\n '302',\n '303',\n '304',\n '305',\n '306',\n '307',\n '701',\n '103',\n '108',\n '109',\n '185',\n '187',\n '913',\n '914',\n '931',\n '936',\n] as const;\n","/**\n * Group array items by a key\n * @param array - Array to group\n * @param key - Property key to group by\n * @returns Object with keys as group names and values as arrays of items\n */\nexport function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {\n return array.reduce(\n (result, item) => {\n const groupKey = String(item[key]);\n if (!result[groupKey]) {\n result[groupKey] = [];\n }\n result[groupKey].push(item);\n return result;\n },\n {} as Record<string, T[]>\n );\n}\n","import { useMemo } from 'react';\nimport { useAccounts } from '@pfm-platform/accounts-data-access';\nimport { GROUPED_ACCOUNT_TYPES } from '../constants';\nimport { groupBy } from '../utils/groupBy';\n\nexport interface AccountGroup {\n name: string;\n accounts: any[]; // Using any for now, will use Account type when available\n sum: number;\n}\n\n/**\n * Group accounts by display type and calculate totals\n *\n * Replaces: accountsStore.accountTypes computed property\n *\n * Business logic:\n * - Groups accounts by display_account_type\n * - Organizes into logical categories (Cash, Credit Cards, Debts, etc.)\n * - Calculates sum of balances for each group\n * - Filters out empty groups\n *\n * @param userId - User ID to fetch accounts for\n * @returns Array of account groups with names, accounts, and sums\n */\nexport function useAccountTypes(userId: string): AccountGroup[] {\n const { data } = useAccounts({ userId });\n\n return useMemo(() => {\n if (!data?.accounts || data.accounts.length === 0) {\n return [];\n }\n\n // Group accounts by display_account_type\n const mappedByType = groupBy(data.accounts, 'display_account_type');\n\n // Organize into predefined groups and calculate sums\n return GROUPED_ACCOUNT_TYPES.map((grouped) => ({\n name: grouped.name,\n accounts: grouped.types.flatMap((type) => mappedByType[type] || []),\n }))\n .filter((group) => group.accounts.length > 0)\n .map((accountType) => ({\n ...accountType,\n sum: accountType.accounts.reduce((sum, account) => sum + (parseFloat(account.balance) || 0), 0),\n }));\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport { useAccounts } from '@pfm-platform/accounts-data-access';\n\nexport interface AccountFilters {\n opened: any[]; // Using any for now, will use Account type when available\n closed: any[];\n}\n\n/**\n * Filter accounts into opened/closed categories\n *\n * Replaces: openedAccounts/closedAccounts computed properties\n *\n * Business logic:\n * - Opened: state === 'active' AND include_in_dashboard === true\n * - Closed: state === 'closed' OR state === 'archived' OR include_in_dashboard === false\n *\n * @param userId - User ID to fetch accounts for\n * @returns Object with opened and closed account arrays\n */\nexport function useAccountFilters(userId: string): AccountFilters {\n const { data } = useAccounts({ userId });\n\n return useMemo(() => {\n if (!data?.accounts || data.accounts.length === 0) {\n return { opened: [], closed: [] };\n }\n\n return {\n opened: data.accounts.filter(\n (account) => account.state === 'active' && account.include_in_dashboard\n ),\n closed: data.accounts.filter(\n (account) =>\n account.state === 'closed' ||\n account.state === 'archived' ||\n !account.include_in_dashboard\n ),\n };\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport { useAccounts } from '@pfm-platform/accounts-data-access';\nimport { useAccountFilters } from './useAccountFilters';\n\nexport interface AccountSummary {\n totalAccounts: number;\n openedCount: number;\n closedCount: number;\n totalBalance: number;\n hasAccounts: boolean;\n}\n\n/**\n * Calculate account summary statistics\n *\n * Replaces: hasAccounts computed property + additional summary logic\n *\n * Business logic:\n * - Counts total, opened, and closed accounts\n * - Calculates total balance across all accounts\n * - Provides hasAccounts boolean for conditional rendering\n *\n * @param userId - User ID to fetch accounts for\n * @returns Object with account counts, balance, and hasAccounts flag\n */\nexport function useAccountSummary(userId: string): AccountSummary | null {\n const { data } = useAccounts({ userId });\n const { opened, closed } = useAccountFilters(userId);\n\n return useMemo(() => {\n if (!data?.accounts) {\n return null;\n }\n\n return {\n totalAccounts: data.accounts.length,\n openedCount: opened.length,\n closedCount: closed.length,\n totalBalance: data.accounts.reduce((sum, account) => sum + (parseFloat(account.balance) || 0), 0),\n hasAccounts: data.accounts.length > 0,\n };\n }, [data, opened, closed]);\n}\n","import { useMemo } from 'react';\nimport { useNetWorth } from '@pfm-platform/accounts-data-access';\n\nexport interface NetWorthSummary {\n netWorth: number;\n netWorthChange: number;\n totalAssets: number;\n totalDebts: number;\n assetCount: number;\n debtCount: number;\n manualAssetCount: number;\n manualDebtCount: number;\n aggregatedAssetCount: number;\n aggregatedDebtCount: number;\n hasAssets: boolean;\n hasDebts: boolean;\n hasManualAccounts: boolean;\n isPositiveNetWorth: boolean;\n isIncreasing: boolean;\n}\n\n/**\n * Calculate net worth summary statistics\n *\n * Business logic:\n * - Parses string amounts to numbers for calculations\n * - Separates manual vs aggregated accounts\n * - Provides counts and totals for assets/debts\n * - Calculates trend indicators (positive, increasing)\n *\n * @param userId - User ID to fetch net worth for\n * @returns Object with net worth metrics and boolean flags\n *\n * @example\n * ```tsx\n * function NetWorthDashboard() {\n * const summary = useNetWorthSummary('user123');\n *\n * if (!summary) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <h2>Net Worth: ${summary.netWorth.toLocaleString()}</h2>\n * <p className={summary.isIncreasing ? 'positive' : 'negative'}>\n * Change: ${summary.netWorthChange.toLocaleString()}\n * </p>\n * <p>Assets: {summary.assetCount} (Manual: {summary.manualAssetCount})</p>\n * <p>Debts: {summary.debtCount} (Manual: {summary.manualDebtCount})</p>\n * </div>\n * );\n * }\n * ```\n */\nexport function useNetWorthSummary(userId: string): NetWorthSummary | null {\n const { data, isLoading } = useNetWorth({ userId });\n\n return useMemo(() => {\n if (!data || isLoading) {\n return null;\n }\n\n // Parse string amounts to numbers\n const netWorth = parseFloat(data.meta.net_worth);\n const netWorthChange = parseFloat(data.meta.net_worth_change);\n const totalAssets = parseFloat(data.meta.total_assets);\n const totalDebts = parseFloat(data.meta.total_debts);\n\n // Count manual vs aggregated accounts\n const manualAssets = data.assets.filter(a => a.additional_networth_account);\n const aggregatedAssets = data.assets.filter(a => !a.additional_networth_account);\n const manualDebts = data.debts.filter(d => d.additional_networth_account);\n const aggregatedDebts = data.debts.filter(d => !d.additional_networth_account);\n\n return {\n netWorth,\n netWorthChange,\n totalAssets,\n totalDebts,\n assetCount: data.assets.length,\n debtCount: data.debts.length,\n manualAssetCount: manualAssets.length,\n manualDebtCount: manualDebts.length,\n aggregatedAssetCount: aggregatedAssets.length,\n aggregatedDebtCount: aggregatedDebts.length,\n hasAssets: data.assets.length > 0,\n hasDebts: data.debts.length > 0,\n hasManualAccounts: manualAssets.length > 0 || manualDebts.length > 0,\n isPositiveNetWorth: netWorth >= 0,\n isIncreasing: netWorthChange > 0,\n };\n }, [data, isLoading]);\n}\n","import { useMemo } from 'react';\nimport { useNetWorth } from '@pfm-platform/accounts-data-access';\nimport type { NetWorthHistory } from '@pfm-platform/shared';\n\nexport interface NetWorthHistoryPoint extends NetWorthHistory {\n totalAsNumber: number;\n totalAssetAsNumber: number;\n totalDebtAsNumber: number;\n sinceLastMonthAsNumber: number;\n date: Date;\n formattedDate: string;\n isPositive: boolean;\n isGrowing: boolean;\n}\n\n/**\n * Process net worth history for charting and analysis\n *\n * Business logic:\n * - Converts string amounts to numbers for calculations\n * - Parses month/year into Date objects for charting\n * - Formats dates for display\n * - Adds boolean flags for trend indicators\n *\n * @param userId - User ID to fetch net worth history for\n * @returns Array of processed history points sorted by date (oldest first)\n *\n * @example\n * ```tsx\n * function NetWorthChart() {\n * const history = useNetWorthHistory('user123');\n *\n * if (!history) return <div>Loading...</div>;\n *\n * return (\n * <LineChart data={history.map(point => ({\n * date: point.formattedDate,\n * value: point.totalAsNumber\n * }))} />\n * );\n * }\n * ```\n *\n * @example Find Recent Growth\n * ```tsx\n * const history = useNetWorthHistory('user123');\n * const recentGrowth = history?.filter(p => p.isGrowing).slice(-3);\n * ```\n */\nexport function useNetWorthHistory(userId: string): NetWorthHistoryPoint[] | null {\n const { data, isLoading } = useNetWorth({ userId });\n\n return useMemo(() => {\n if (!data || isLoading || !data.networth_histories) {\n return null;\n }\n\n return data.networth_histories\n .map((history) => {\n const totalAsNumber = parseFloat(history.total);\n const totalAssetAsNumber = parseFloat(history.total_asset);\n const totalDebtAsNumber = parseFloat(history.total_debt);\n const sinceLastMonthAsNumber = parseFloat(history.since_last_month);\n\n // Parse month/year into Date (use first day of month)\n const year = parseInt(history.year, 10);\n const month = parseInt(history.month, 10) - 1; // JS months are 0-indexed\n const date = new Date(year, month, 1);\n\n // Format for display (e.g., \"Apr 2019\")\n const formattedDate = date.toLocaleDateString('en-US', {\n month: 'short',\n year: 'numeric',\n });\n\n return {\n ...history,\n totalAsNumber,\n totalAssetAsNumber,\n totalDebtAsNumber,\n sinceLastMonthAsNumber,\n date,\n formattedDate,\n isPositive: totalAsNumber >= 0,\n isGrowing: sinceLastMonthAsNumber > 0,\n };\n })\n .sort((a, b) => a.date.getTime() - b.date.getTime()); // Sort oldest to newest\n }, [data, isLoading]);\n}\n"]}
@@ -0,0 +1,214 @@
1
+ import { NetWorthHistory } from '@pfm-platform/shared';
2
+
3
+ interface AccountGroup {
4
+ name: string;
5
+ accounts: any[];
6
+ sum: number;
7
+ }
8
+ /**
9
+ * Group accounts by display type and calculate totals
10
+ *
11
+ * Replaces: accountsStore.accountTypes computed property
12
+ *
13
+ * Business logic:
14
+ * - Groups accounts by display_account_type
15
+ * - Organizes into logical categories (Cash, Credit Cards, Debts, etc.)
16
+ * - Calculates sum of balances for each group
17
+ * - Filters out empty groups
18
+ *
19
+ * @param userId - User ID to fetch accounts for
20
+ * @returns Array of account groups with names, accounts, and sums
21
+ */
22
+ declare function useAccountTypes(userId: string): AccountGroup[];
23
+
24
+ interface AccountFilters {
25
+ opened: any[];
26
+ closed: any[];
27
+ }
28
+ /**
29
+ * Filter accounts into opened/closed categories
30
+ *
31
+ * Replaces: openedAccounts/closedAccounts computed properties
32
+ *
33
+ * Business logic:
34
+ * - Opened: state === 'active' AND include_in_dashboard === true
35
+ * - Closed: state === 'closed' OR state === 'archived' OR include_in_dashboard === false
36
+ *
37
+ * @param userId - User ID to fetch accounts for
38
+ * @returns Object with opened and closed account arrays
39
+ */
40
+ declare function useAccountFilters(userId: string): AccountFilters;
41
+
42
+ interface AccountSummary {
43
+ totalAccounts: number;
44
+ openedCount: number;
45
+ closedCount: number;
46
+ totalBalance: number;
47
+ hasAccounts: boolean;
48
+ }
49
+ /**
50
+ * Calculate account summary statistics
51
+ *
52
+ * Replaces: hasAccounts computed property + additional summary logic
53
+ *
54
+ * Business logic:
55
+ * - Counts total, opened, and closed accounts
56
+ * - Calculates total balance across all accounts
57
+ * - Provides hasAccounts boolean for conditional rendering
58
+ *
59
+ * @param userId - User ID to fetch accounts for
60
+ * @returns Object with account counts, balance, and hasAccounts flag
61
+ */
62
+ declare function useAccountSummary(userId: string): AccountSummary | null;
63
+
64
+ interface NetWorthSummary {
65
+ netWorth: number;
66
+ netWorthChange: number;
67
+ totalAssets: number;
68
+ totalDebts: number;
69
+ assetCount: number;
70
+ debtCount: number;
71
+ manualAssetCount: number;
72
+ manualDebtCount: number;
73
+ aggregatedAssetCount: number;
74
+ aggregatedDebtCount: number;
75
+ hasAssets: boolean;
76
+ hasDebts: boolean;
77
+ hasManualAccounts: boolean;
78
+ isPositiveNetWorth: boolean;
79
+ isIncreasing: boolean;
80
+ }
81
+ /**
82
+ * Calculate net worth summary statistics
83
+ *
84
+ * Business logic:
85
+ * - Parses string amounts to numbers for calculations
86
+ * - Separates manual vs aggregated accounts
87
+ * - Provides counts and totals for assets/debts
88
+ * - Calculates trend indicators (positive, increasing)
89
+ *
90
+ * @param userId - User ID to fetch net worth for
91
+ * @returns Object with net worth metrics and boolean flags
92
+ *
93
+ * @example
94
+ * ```tsx
95
+ * function NetWorthDashboard() {
96
+ * const summary = useNetWorthSummary('user123');
97
+ *
98
+ * if (!summary) return <div>Loading...</div>;
99
+ *
100
+ * return (
101
+ * <div>
102
+ * <h2>Net Worth: ${summary.netWorth.toLocaleString()}</h2>
103
+ * <p className={summary.isIncreasing ? 'positive' : 'negative'}>
104
+ * Change: ${summary.netWorthChange.toLocaleString()}
105
+ * </p>
106
+ * <p>Assets: {summary.assetCount} (Manual: {summary.manualAssetCount})</p>
107
+ * <p>Debts: {summary.debtCount} (Manual: {summary.manualDebtCount})</p>
108
+ * </div>
109
+ * );
110
+ * }
111
+ * ```
112
+ */
113
+ declare function useNetWorthSummary(userId: string): NetWorthSummary | null;
114
+
115
+ interface NetWorthHistoryPoint extends NetWorthHistory {
116
+ totalAsNumber: number;
117
+ totalAssetAsNumber: number;
118
+ totalDebtAsNumber: number;
119
+ sinceLastMonthAsNumber: number;
120
+ date: Date;
121
+ formattedDate: string;
122
+ isPositive: boolean;
123
+ isGrowing: boolean;
124
+ }
125
+ /**
126
+ * Process net worth history for charting and analysis
127
+ *
128
+ * Business logic:
129
+ * - Converts string amounts to numbers for calculations
130
+ * - Parses month/year into Date objects for charting
131
+ * - Formats dates for display
132
+ * - Adds boolean flags for trend indicators
133
+ *
134
+ * @param userId - User ID to fetch net worth history for
135
+ * @returns Array of processed history points sorted by date (oldest first)
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * function NetWorthChart() {
140
+ * const history = useNetWorthHistory('user123');
141
+ *
142
+ * if (!history) return <div>Loading...</div>;
143
+ *
144
+ * return (
145
+ * <LineChart data={history.map(point => ({
146
+ * date: point.formattedDate,
147
+ * value: point.totalAsNumber
148
+ * }))} />
149
+ * );
150
+ * }
151
+ * ```
152
+ *
153
+ * @example Find Recent Growth
154
+ * ```tsx
155
+ * const history = useNetWorthHistory('user123');
156
+ * const recentGrowth = history?.filter(p => p.isGrowing).slice(-3);
157
+ * ```
158
+ */
159
+ declare function useNetWorthHistory(userId: string): NetWorthHistoryPoint[] | null;
160
+
161
+ /**
162
+ * Account type display names
163
+ * Maps account_type to human-readable names
164
+ */
165
+ declare const ACCOUNT_TYPE_NAMES: {
166
+ readonly checking: "Checking";
167
+ readonly savings: "Savings";
168
+ readonly cards: "Card";
169
+ readonly student_loans: "Student Loan";
170
+ readonly bill: "Bill";
171
+ readonly autos: "Auto";
172
+ readonly home: "Mortgage";
173
+ readonly investment: "Investment";
174
+ readonly loan: "Loan";
175
+ readonly asset: "Asset";
176
+ readonly cd: "Certificate";
177
+ };
178
+ /**
179
+ * Grouped account types for categorization
180
+ * Used by useAccountTypes to organize accounts into logical groups
181
+ */
182
+ declare const GROUPED_ACCOUNT_TYPES: readonly [{
183
+ readonly name: "Cash";
184
+ readonly types: readonly ["checking", "savings", "money_market"];
185
+ }, {
186
+ readonly name: "Credit Cards";
187
+ readonly types: readonly ["cards"];
188
+ }, {
189
+ readonly name: "Debts";
190
+ readonly types: readonly ["autos", "home", "loan", "student_loans", "creditline"];
191
+ }, {
192
+ readonly name: "Investments";
193
+ readonly types: readonly ["investment"];
194
+ }, {
195
+ readonly name: "Assets";
196
+ readonly types: readonly ["asset", "cd"];
197
+ }, {
198
+ readonly name: "Bills";
199
+ readonly types: readonly ["bill"];
200
+ }];
201
+ /**
202
+ * User error codes that indicate credentials need updating
203
+ */
204
+ declare const USER_ERROR_CODES: readonly ["201", "203", "204", "209", "300", "301", "302", "303", "304", "305", "306", "307", "701", "103", "108", "109", "185", "187", "913", "914", "931", "936"];
205
+
206
+ /**
207
+ * Group array items by a key
208
+ * @param array - Array to group
209
+ * @param key - Property key to group by
210
+ * @returns Object with keys as group names and values as arrays of items
211
+ */
212
+ declare function groupBy<T>(array: T[], key: keyof T): Record<string, T[]>;
213
+
214
+ export { ACCOUNT_TYPE_NAMES, type AccountFilters, type AccountGroup, type AccountSummary, GROUPED_ACCOUNT_TYPES, type NetWorthHistoryPoint, type NetWorthSummary, USER_ERROR_CODES, groupBy, useAccountFilters, useAccountSummary, useAccountTypes, useNetWorthHistory, useNetWorthSummary };
@@ -0,0 +1,214 @@
1
+ import { NetWorthHistory } from '@pfm-platform/shared';
2
+
3
+ interface AccountGroup {
4
+ name: string;
5
+ accounts: any[];
6
+ sum: number;
7
+ }
8
+ /**
9
+ * Group accounts by display type and calculate totals
10
+ *
11
+ * Replaces: accountsStore.accountTypes computed property
12
+ *
13
+ * Business logic:
14
+ * - Groups accounts by display_account_type
15
+ * - Organizes into logical categories (Cash, Credit Cards, Debts, etc.)
16
+ * - Calculates sum of balances for each group
17
+ * - Filters out empty groups
18
+ *
19
+ * @param userId - User ID to fetch accounts for
20
+ * @returns Array of account groups with names, accounts, and sums
21
+ */
22
+ declare function useAccountTypes(userId: string): AccountGroup[];
23
+
24
+ interface AccountFilters {
25
+ opened: any[];
26
+ closed: any[];
27
+ }
28
+ /**
29
+ * Filter accounts into opened/closed categories
30
+ *
31
+ * Replaces: openedAccounts/closedAccounts computed properties
32
+ *
33
+ * Business logic:
34
+ * - Opened: state === 'active' AND include_in_dashboard === true
35
+ * - Closed: state === 'closed' OR state === 'archived' OR include_in_dashboard === false
36
+ *
37
+ * @param userId - User ID to fetch accounts for
38
+ * @returns Object with opened and closed account arrays
39
+ */
40
+ declare function useAccountFilters(userId: string): AccountFilters;
41
+
42
+ interface AccountSummary {
43
+ totalAccounts: number;
44
+ openedCount: number;
45
+ closedCount: number;
46
+ totalBalance: number;
47
+ hasAccounts: boolean;
48
+ }
49
+ /**
50
+ * Calculate account summary statistics
51
+ *
52
+ * Replaces: hasAccounts computed property + additional summary logic
53
+ *
54
+ * Business logic:
55
+ * - Counts total, opened, and closed accounts
56
+ * - Calculates total balance across all accounts
57
+ * - Provides hasAccounts boolean for conditional rendering
58
+ *
59
+ * @param userId - User ID to fetch accounts for
60
+ * @returns Object with account counts, balance, and hasAccounts flag
61
+ */
62
+ declare function useAccountSummary(userId: string): AccountSummary | null;
63
+
64
+ interface NetWorthSummary {
65
+ netWorth: number;
66
+ netWorthChange: number;
67
+ totalAssets: number;
68
+ totalDebts: number;
69
+ assetCount: number;
70
+ debtCount: number;
71
+ manualAssetCount: number;
72
+ manualDebtCount: number;
73
+ aggregatedAssetCount: number;
74
+ aggregatedDebtCount: number;
75
+ hasAssets: boolean;
76
+ hasDebts: boolean;
77
+ hasManualAccounts: boolean;
78
+ isPositiveNetWorth: boolean;
79
+ isIncreasing: boolean;
80
+ }
81
+ /**
82
+ * Calculate net worth summary statistics
83
+ *
84
+ * Business logic:
85
+ * - Parses string amounts to numbers for calculations
86
+ * - Separates manual vs aggregated accounts
87
+ * - Provides counts and totals for assets/debts
88
+ * - Calculates trend indicators (positive, increasing)
89
+ *
90
+ * @param userId - User ID to fetch net worth for
91
+ * @returns Object with net worth metrics and boolean flags
92
+ *
93
+ * @example
94
+ * ```tsx
95
+ * function NetWorthDashboard() {
96
+ * const summary = useNetWorthSummary('user123');
97
+ *
98
+ * if (!summary) return <div>Loading...</div>;
99
+ *
100
+ * return (
101
+ * <div>
102
+ * <h2>Net Worth: ${summary.netWorth.toLocaleString()}</h2>
103
+ * <p className={summary.isIncreasing ? 'positive' : 'negative'}>
104
+ * Change: ${summary.netWorthChange.toLocaleString()}
105
+ * </p>
106
+ * <p>Assets: {summary.assetCount} (Manual: {summary.manualAssetCount})</p>
107
+ * <p>Debts: {summary.debtCount} (Manual: {summary.manualDebtCount})</p>
108
+ * </div>
109
+ * );
110
+ * }
111
+ * ```
112
+ */
113
+ declare function useNetWorthSummary(userId: string): NetWorthSummary | null;
114
+
115
+ interface NetWorthHistoryPoint extends NetWorthHistory {
116
+ totalAsNumber: number;
117
+ totalAssetAsNumber: number;
118
+ totalDebtAsNumber: number;
119
+ sinceLastMonthAsNumber: number;
120
+ date: Date;
121
+ formattedDate: string;
122
+ isPositive: boolean;
123
+ isGrowing: boolean;
124
+ }
125
+ /**
126
+ * Process net worth history for charting and analysis
127
+ *
128
+ * Business logic:
129
+ * - Converts string amounts to numbers for calculations
130
+ * - Parses month/year into Date objects for charting
131
+ * - Formats dates for display
132
+ * - Adds boolean flags for trend indicators
133
+ *
134
+ * @param userId - User ID to fetch net worth history for
135
+ * @returns Array of processed history points sorted by date (oldest first)
136
+ *
137
+ * @example
138
+ * ```tsx
139
+ * function NetWorthChart() {
140
+ * const history = useNetWorthHistory('user123');
141
+ *
142
+ * if (!history) return <div>Loading...</div>;
143
+ *
144
+ * return (
145
+ * <LineChart data={history.map(point => ({
146
+ * date: point.formattedDate,
147
+ * value: point.totalAsNumber
148
+ * }))} />
149
+ * );
150
+ * }
151
+ * ```
152
+ *
153
+ * @example Find Recent Growth
154
+ * ```tsx
155
+ * const history = useNetWorthHistory('user123');
156
+ * const recentGrowth = history?.filter(p => p.isGrowing).slice(-3);
157
+ * ```
158
+ */
159
+ declare function useNetWorthHistory(userId: string): NetWorthHistoryPoint[] | null;
160
+
161
+ /**
162
+ * Account type display names
163
+ * Maps account_type to human-readable names
164
+ */
165
+ declare const ACCOUNT_TYPE_NAMES: {
166
+ readonly checking: "Checking";
167
+ readonly savings: "Savings";
168
+ readonly cards: "Card";
169
+ readonly student_loans: "Student Loan";
170
+ readonly bill: "Bill";
171
+ readonly autos: "Auto";
172
+ readonly home: "Mortgage";
173
+ readonly investment: "Investment";
174
+ readonly loan: "Loan";
175
+ readonly asset: "Asset";
176
+ readonly cd: "Certificate";
177
+ };
178
+ /**
179
+ * Grouped account types for categorization
180
+ * Used by useAccountTypes to organize accounts into logical groups
181
+ */
182
+ declare const GROUPED_ACCOUNT_TYPES: readonly [{
183
+ readonly name: "Cash";
184
+ readonly types: readonly ["checking", "savings", "money_market"];
185
+ }, {
186
+ readonly name: "Credit Cards";
187
+ readonly types: readonly ["cards"];
188
+ }, {
189
+ readonly name: "Debts";
190
+ readonly types: readonly ["autos", "home", "loan", "student_loans", "creditline"];
191
+ }, {
192
+ readonly name: "Investments";
193
+ readonly types: readonly ["investment"];
194
+ }, {
195
+ readonly name: "Assets";
196
+ readonly types: readonly ["asset", "cd"];
197
+ }, {
198
+ readonly name: "Bills";
199
+ readonly types: readonly ["bill"];
200
+ }];
201
+ /**
202
+ * User error codes that indicate credentials need updating
203
+ */
204
+ declare const USER_ERROR_CODES: readonly ["201", "203", "204", "209", "300", "301", "302", "303", "304", "305", "306", "307", "701", "103", "108", "109", "185", "187", "913", "914", "931", "936"];
205
+
206
+ /**
207
+ * Group array items by a key
208
+ * @param array - Array to group
209
+ * @param key - Property key to group by
210
+ * @returns Object with keys as group names and values as arrays of items
211
+ */
212
+ declare function groupBy<T>(array: T[], key: keyof T): Record<string, T[]>;
213
+
214
+ export { ACCOUNT_TYPE_NAMES, type AccountFilters, type AccountGroup, type AccountSummary, GROUPED_ACCOUNT_TYPES, type NetWorthHistoryPoint, type NetWorthSummary, USER_ERROR_CODES, groupBy, useAccountFilters, useAccountSummary, useAccountTypes, useNetWorthHistory, useNetWorthSummary };
package/dist/index.js ADDED
@@ -0,0 +1,203 @@
1
+ import { useMemo } from 'react';
2
+ import { useAccounts, useNetWorth } from '@pfm-platform/accounts-data-access';
3
+
4
+ // src/hooks/useAccountTypes.ts
5
+
6
+ // src/constants.ts
7
+ var ACCOUNT_TYPE_NAMES = {
8
+ checking: "Checking",
9
+ savings: "Savings",
10
+ cards: "Card",
11
+ student_loans: "Student Loan",
12
+ bill: "Bill",
13
+ autos: "Auto",
14
+ home: "Mortgage",
15
+ investment: "Investment",
16
+ loan: "Loan",
17
+ asset: "Asset",
18
+ cd: "Certificate"
19
+ };
20
+ var GROUPED_ACCOUNT_TYPES = [
21
+ {
22
+ name: "Cash",
23
+ types: ["checking", "savings", "money_market"]
24
+ },
25
+ {
26
+ name: "Credit Cards",
27
+ types: ["cards"]
28
+ },
29
+ {
30
+ name: "Debts",
31
+ types: ["autos", "home", "loan", "student_loans", "creditline"]
32
+ },
33
+ {
34
+ name: "Investments",
35
+ types: ["investment"]
36
+ },
37
+ {
38
+ name: "Assets",
39
+ types: ["asset", "cd"]
40
+ },
41
+ {
42
+ name: "Bills",
43
+ types: ["bill"]
44
+ }
45
+ ];
46
+ var USER_ERROR_CODES = [
47
+ "201",
48
+ "203",
49
+ "204",
50
+ "209",
51
+ "300",
52
+ "301",
53
+ "302",
54
+ "303",
55
+ "304",
56
+ "305",
57
+ "306",
58
+ "307",
59
+ "701",
60
+ "103",
61
+ "108",
62
+ "109",
63
+ "185",
64
+ "187",
65
+ "913",
66
+ "914",
67
+ "931",
68
+ "936"
69
+ ];
70
+
71
+ // src/utils/groupBy.ts
72
+ function groupBy(array, key) {
73
+ return array.reduce(
74
+ (result, item) => {
75
+ const groupKey = String(item[key]);
76
+ if (!result[groupKey]) {
77
+ result[groupKey] = [];
78
+ }
79
+ result[groupKey].push(item);
80
+ return result;
81
+ },
82
+ {}
83
+ );
84
+ }
85
+
86
+ // src/hooks/useAccountTypes.ts
87
+ function useAccountTypes(userId) {
88
+ const { data } = useAccounts({ userId });
89
+ return useMemo(() => {
90
+ if (!data?.accounts || data.accounts.length === 0) {
91
+ return [];
92
+ }
93
+ const mappedByType = groupBy(data.accounts, "display_account_type");
94
+ return GROUPED_ACCOUNT_TYPES.map((grouped) => ({
95
+ name: grouped.name,
96
+ accounts: grouped.types.flatMap((type) => mappedByType[type] || [])
97
+ })).filter((group) => group.accounts.length > 0).map((accountType) => ({
98
+ ...accountType,
99
+ sum: accountType.accounts.reduce((sum, account) => sum + (parseFloat(account.balance) || 0), 0)
100
+ }));
101
+ }, [data]);
102
+ }
103
+ function useAccountFilters(userId) {
104
+ const { data } = useAccounts({ userId });
105
+ return useMemo(() => {
106
+ if (!data?.accounts || data.accounts.length === 0) {
107
+ return { opened: [], closed: [] };
108
+ }
109
+ return {
110
+ opened: data.accounts.filter(
111
+ (account) => account.state === "active" && account.include_in_dashboard
112
+ ),
113
+ closed: data.accounts.filter(
114
+ (account) => account.state === "closed" || account.state === "archived" || !account.include_in_dashboard
115
+ )
116
+ };
117
+ }, [data]);
118
+ }
119
+ function useAccountSummary(userId) {
120
+ const { data } = useAccounts({ userId });
121
+ const { opened, closed } = useAccountFilters(userId);
122
+ return useMemo(() => {
123
+ if (!data?.accounts) {
124
+ return null;
125
+ }
126
+ return {
127
+ totalAccounts: data.accounts.length,
128
+ openedCount: opened.length,
129
+ closedCount: closed.length,
130
+ totalBalance: data.accounts.reduce((sum, account) => sum + (parseFloat(account.balance) || 0), 0),
131
+ hasAccounts: data.accounts.length > 0
132
+ };
133
+ }, [data, opened, closed]);
134
+ }
135
+ function useNetWorthSummary(userId) {
136
+ const { data, isLoading } = useNetWorth({ userId });
137
+ return useMemo(() => {
138
+ if (!data || isLoading) {
139
+ return null;
140
+ }
141
+ const netWorth = parseFloat(data.meta.net_worth);
142
+ const netWorthChange = parseFloat(data.meta.net_worth_change);
143
+ const totalAssets = parseFloat(data.meta.total_assets);
144
+ const totalDebts = parseFloat(data.meta.total_debts);
145
+ const manualAssets = data.assets.filter((a) => a.additional_networth_account);
146
+ const aggregatedAssets = data.assets.filter((a) => !a.additional_networth_account);
147
+ const manualDebts = data.debts.filter((d) => d.additional_networth_account);
148
+ const aggregatedDebts = data.debts.filter((d) => !d.additional_networth_account);
149
+ return {
150
+ netWorth,
151
+ netWorthChange,
152
+ totalAssets,
153
+ totalDebts,
154
+ assetCount: data.assets.length,
155
+ debtCount: data.debts.length,
156
+ manualAssetCount: manualAssets.length,
157
+ manualDebtCount: manualDebts.length,
158
+ aggregatedAssetCount: aggregatedAssets.length,
159
+ aggregatedDebtCount: aggregatedDebts.length,
160
+ hasAssets: data.assets.length > 0,
161
+ hasDebts: data.debts.length > 0,
162
+ hasManualAccounts: manualAssets.length > 0 || manualDebts.length > 0,
163
+ isPositiveNetWorth: netWorth >= 0,
164
+ isIncreasing: netWorthChange > 0
165
+ };
166
+ }, [data, isLoading]);
167
+ }
168
+ function useNetWorthHistory(userId) {
169
+ const { data, isLoading } = useNetWorth({ userId });
170
+ return useMemo(() => {
171
+ if (!data || isLoading || !data.networth_histories) {
172
+ return null;
173
+ }
174
+ return data.networth_histories.map((history) => {
175
+ const totalAsNumber = parseFloat(history.total);
176
+ const totalAssetAsNumber = parseFloat(history.total_asset);
177
+ const totalDebtAsNumber = parseFloat(history.total_debt);
178
+ const sinceLastMonthAsNumber = parseFloat(history.since_last_month);
179
+ const year = parseInt(history.year, 10);
180
+ const month = parseInt(history.month, 10) - 1;
181
+ const date = new Date(year, month, 1);
182
+ const formattedDate = date.toLocaleDateString("en-US", {
183
+ month: "short",
184
+ year: "numeric"
185
+ });
186
+ return {
187
+ ...history,
188
+ totalAsNumber,
189
+ totalAssetAsNumber,
190
+ totalDebtAsNumber,
191
+ sinceLastMonthAsNumber,
192
+ date,
193
+ formattedDate,
194
+ isPositive: totalAsNumber >= 0,
195
+ isGrowing: sinceLastMonthAsNumber > 0
196
+ };
197
+ }).sort((a, b) => a.date.getTime() - b.date.getTime());
198
+ }, [data, isLoading]);
199
+ }
200
+
201
+ export { ACCOUNT_TYPE_NAMES, GROUPED_ACCOUNT_TYPES, USER_ERROR_CODES, groupBy, useAccountFilters, useAccountSummary, useAccountTypes, useNetWorthHistory, useNetWorthSummary };
202
+ //# sourceMappingURL=index.js.map
203
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils/groupBy.ts","../src/hooks/useAccountTypes.ts","../src/hooks/useAccountFilters.ts","../src/hooks/useAccountSummary.ts","../src/hooks/useNetWorthSummary.ts","../src/hooks/useNetWorthHistory.ts"],"names":["useAccounts","useMemo","useNetWorth"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Account type display names\n * Maps account_type to human-readable names\n */\nexport const ACCOUNT_TYPE_NAMES = {\n checking: 'Checking',\n savings: 'Savings',\n cards: 'Card',\n student_loans: 'Student Loan',\n bill: 'Bill',\n autos: 'Auto',\n home: 'Mortgage',\n investment: 'Investment',\n loan: 'Loan',\n asset: 'Asset',\n cd: 'Certificate',\n} as const;\n\n/**\n * Grouped account types for categorization\n * Used by useAccountTypes to organize accounts into logical groups\n */\nexport const GROUPED_ACCOUNT_TYPES = [\n {\n name: 'Cash',\n types: ['checking', 'savings', 'money_market'],\n },\n {\n name: 'Credit Cards',\n types: ['cards'],\n },\n {\n name: 'Debts',\n types: ['autos', 'home', 'loan', 'student_loans', 'creditline'],\n },\n {\n name: 'Investments',\n types: ['investment'],\n },\n {\n name: 'Assets',\n types: ['asset', 'cd'],\n },\n {\n name: 'Bills',\n types: ['bill'],\n },\n] as const;\n\n/**\n * User error codes that indicate credentials need updating\n */\nexport const USER_ERROR_CODES = [\n '201',\n '203',\n '204',\n '209',\n '300',\n '301',\n '302',\n '303',\n '304',\n '305',\n '306',\n '307',\n '701',\n '103',\n '108',\n '109',\n '185',\n '187',\n '913',\n '914',\n '931',\n '936',\n] as const;\n","/**\n * Group array items by a key\n * @param array - Array to group\n * @param key - Property key to group by\n * @returns Object with keys as group names and values as arrays of items\n */\nexport function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {\n return array.reduce(\n (result, item) => {\n const groupKey = String(item[key]);\n if (!result[groupKey]) {\n result[groupKey] = [];\n }\n result[groupKey].push(item);\n return result;\n },\n {} as Record<string, T[]>\n );\n}\n","import { useMemo } from 'react';\nimport { useAccounts } from '@pfm-platform/accounts-data-access';\nimport { GROUPED_ACCOUNT_TYPES } from '../constants';\nimport { groupBy } from '../utils/groupBy';\n\nexport interface AccountGroup {\n name: string;\n accounts: any[]; // Using any for now, will use Account type when available\n sum: number;\n}\n\n/**\n * Group accounts by display type and calculate totals\n *\n * Replaces: accountsStore.accountTypes computed property\n *\n * Business logic:\n * - Groups accounts by display_account_type\n * - Organizes into logical categories (Cash, Credit Cards, Debts, etc.)\n * - Calculates sum of balances for each group\n * - Filters out empty groups\n *\n * @param userId - User ID to fetch accounts for\n * @returns Array of account groups with names, accounts, and sums\n */\nexport function useAccountTypes(userId: string): AccountGroup[] {\n const { data } = useAccounts({ userId });\n\n return useMemo(() => {\n if (!data?.accounts || data.accounts.length === 0) {\n return [];\n }\n\n // Group accounts by display_account_type\n const mappedByType = groupBy(data.accounts, 'display_account_type');\n\n // Organize into predefined groups and calculate sums\n return GROUPED_ACCOUNT_TYPES.map((grouped) => ({\n name: grouped.name,\n accounts: grouped.types.flatMap((type) => mappedByType[type] || []),\n }))\n .filter((group) => group.accounts.length > 0)\n .map((accountType) => ({\n ...accountType,\n sum: accountType.accounts.reduce((sum, account) => sum + (parseFloat(account.balance) || 0), 0),\n }));\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport { useAccounts } from '@pfm-platform/accounts-data-access';\n\nexport interface AccountFilters {\n opened: any[]; // Using any for now, will use Account type when available\n closed: any[];\n}\n\n/**\n * Filter accounts into opened/closed categories\n *\n * Replaces: openedAccounts/closedAccounts computed properties\n *\n * Business logic:\n * - Opened: state === 'active' AND include_in_dashboard === true\n * - Closed: state === 'closed' OR state === 'archived' OR include_in_dashboard === false\n *\n * @param userId - User ID to fetch accounts for\n * @returns Object with opened and closed account arrays\n */\nexport function useAccountFilters(userId: string): AccountFilters {\n const { data } = useAccounts({ userId });\n\n return useMemo(() => {\n if (!data?.accounts || data.accounts.length === 0) {\n return { opened: [], closed: [] };\n }\n\n return {\n opened: data.accounts.filter(\n (account) => account.state === 'active' && account.include_in_dashboard\n ),\n closed: data.accounts.filter(\n (account) =>\n account.state === 'closed' ||\n account.state === 'archived' ||\n !account.include_in_dashboard\n ),\n };\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport { useAccounts } from '@pfm-platform/accounts-data-access';\nimport { useAccountFilters } from './useAccountFilters';\n\nexport interface AccountSummary {\n totalAccounts: number;\n openedCount: number;\n closedCount: number;\n totalBalance: number;\n hasAccounts: boolean;\n}\n\n/**\n * Calculate account summary statistics\n *\n * Replaces: hasAccounts computed property + additional summary logic\n *\n * Business logic:\n * - Counts total, opened, and closed accounts\n * - Calculates total balance across all accounts\n * - Provides hasAccounts boolean for conditional rendering\n *\n * @param userId - User ID to fetch accounts for\n * @returns Object with account counts, balance, and hasAccounts flag\n */\nexport function useAccountSummary(userId: string): AccountSummary | null {\n const { data } = useAccounts({ userId });\n const { opened, closed } = useAccountFilters(userId);\n\n return useMemo(() => {\n if (!data?.accounts) {\n return null;\n }\n\n return {\n totalAccounts: data.accounts.length,\n openedCount: opened.length,\n closedCount: closed.length,\n totalBalance: data.accounts.reduce((sum, account) => sum + (parseFloat(account.balance) || 0), 0),\n hasAccounts: data.accounts.length > 0,\n };\n }, [data, opened, closed]);\n}\n","import { useMemo } from 'react';\nimport { useNetWorth } from '@pfm-platform/accounts-data-access';\n\nexport interface NetWorthSummary {\n netWorth: number;\n netWorthChange: number;\n totalAssets: number;\n totalDebts: number;\n assetCount: number;\n debtCount: number;\n manualAssetCount: number;\n manualDebtCount: number;\n aggregatedAssetCount: number;\n aggregatedDebtCount: number;\n hasAssets: boolean;\n hasDebts: boolean;\n hasManualAccounts: boolean;\n isPositiveNetWorth: boolean;\n isIncreasing: boolean;\n}\n\n/**\n * Calculate net worth summary statistics\n *\n * Business logic:\n * - Parses string amounts to numbers for calculations\n * - Separates manual vs aggregated accounts\n * - Provides counts and totals for assets/debts\n * - Calculates trend indicators (positive, increasing)\n *\n * @param userId - User ID to fetch net worth for\n * @returns Object with net worth metrics and boolean flags\n *\n * @example\n * ```tsx\n * function NetWorthDashboard() {\n * const summary = useNetWorthSummary('user123');\n *\n * if (!summary) return <div>Loading...</div>;\n *\n * return (\n * <div>\n * <h2>Net Worth: ${summary.netWorth.toLocaleString()}</h2>\n * <p className={summary.isIncreasing ? 'positive' : 'negative'}>\n * Change: ${summary.netWorthChange.toLocaleString()}\n * </p>\n * <p>Assets: {summary.assetCount} (Manual: {summary.manualAssetCount})</p>\n * <p>Debts: {summary.debtCount} (Manual: {summary.manualDebtCount})</p>\n * </div>\n * );\n * }\n * ```\n */\nexport function useNetWorthSummary(userId: string): NetWorthSummary | null {\n const { data, isLoading } = useNetWorth({ userId });\n\n return useMemo(() => {\n if (!data || isLoading) {\n return null;\n }\n\n // Parse string amounts to numbers\n const netWorth = parseFloat(data.meta.net_worth);\n const netWorthChange = parseFloat(data.meta.net_worth_change);\n const totalAssets = parseFloat(data.meta.total_assets);\n const totalDebts = parseFloat(data.meta.total_debts);\n\n // Count manual vs aggregated accounts\n const manualAssets = data.assets.filter(a => a.additional_networth_account);\n const aggregatedAssets = data.assets.filter(a => !a.additional_networth_account);\n const manualDebts = data.debts.filter(d => d.additional_networth_account);\n const aggregatedDebts = data.debts.filter(d => !d.additional_networth_account);\n\n return {\n netWorth,\n netWorthChange,\n totalAssets,\n totalDebts,\n assetCount: data.assets.length,\n debtCount: data.debts.length,\n manualAssetCount: manualAssets.length,\n manualDebtCount: manualDebts.length,\n aggregatedAssetCount: aggregatedAssets.length,\n aggregatedDebtCount: aggregatedDebts.length,\n hasAssets: data.assets.length > 0,\n hasDebts: data.debts.length > 0,\n hasManualAccounts: manualAssets.length > 0 || manualDebts.length > 0,\n isPositiveNetWorth: netWorth >= 0,\n isIncreasing: netWorthChange > 0,\n };\n }, [data, isLoading]);\n}\n","import { useMemo } from 'react';\nimport { useNetWorth } from '@pfm-platform/accounts-data-access';\nimport type { NetWorthHistory } from '@pfm-platform/shared';\n\nexport interface NetWorthHistoryPoint extends NetWorthHistory {\n totalAsNumber: number;\n totalAssetAsNumber: number;\n totalDebtAsNumber: number;\n sinceLastMonthAsNumber: number;\n date: Date;\n formattedDate: string;\n isPositive: boolean;\n isGrowing: boolean;\n}\n\n/**\n * Process net worth history for charting and analysis\n *\n * Business logic:\n * - Converts string amounts to numbers for calculations\n * - Parses month/year into Date objects for charting\n * - Formats dates for display\n * - Adds boolean flags for trend indicators\n *\n * @param userId - User ID to fetch net worth history for\n * @returns Array of processed history points sorted by date (oldest first)\n *\n * @example\n * ```tsx\n * function NetWorthChart() {\n * const history = useNetWorthHistory('user123');\n *\n * if (!history) return <div>Loading...</div>;\n *\n * return (\n * <LineChart data={history.map(point => ({\n * date: point.formattedDate,\n * value: point.totalAsNumber\n * }))} />\n * );\n * }\n * ```\n *\n * @example Find Recent Growth\n * ```tsx\n * const history = useNetWorthHistory('user123');\n * const recentGrowth = history?.filter(p => p.isGrowing).slice(-3);\n * ```\n */\nexport function useNetWorthHistory(userId: string): NetWorthHistoryPoint[] | null {\n const { data, isLoading } = useNetWorth({ userId });\n\n return useMemo(() => {\n if (!data || isLoading || !data.networth_histories) {\n return null;\n }\n\n return data.networth_histories\n .map((history) => {\n const totalAsNumber = parseFloat(history.total);\n const totalAssetAsNumber = parseFloat(history.total_asset);\n const totalDebtAsNumber = parseFloat(history.total_debt);\n const sinceLastMonthAsNumber = parseFloat(history.since_last_month);\n\n // Parse month/year into Date (use first day of month)\n const year = parseInt(history.year, 10);\n const month = parseInt(history.month, 10) - 1; // JS months are 0-indexed\n const date = new Date(year, month, 1);\n\n // Format for display (e.g., \"Apr 2019\")\n const formattedDate = date.toLocaleDateString('en-US', {\n month: 'short',\n year: 'numeric',\n });\n\n return {\n ...history,\n totalAsNumber,\n totalAssetAsNumber,\n totalDebtAsNumber,\n sinceLastMonthAsNumber,\n date,\n formattedDate,\n isPositive: totalAsNumber >= 0,\n isGrowing: sinceLastMonthAsNumber > 0,\n };\n })\n .sort((a, b) => a.date.getTime() - b.date.getTime()); // Sort oldest to newest\n }, [data, isLoading]);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@pfm-platform/accounts-feature",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "dependencies": {
7
+ "react": "19.2.0",
8
+ "@pfm-platform/accounts-data-access": "0.1.1",
9
+ "@pfm-platform/shared": "0.0.1"
10
+ },
11
+ "devDependencies": {
12
+ "@tanstack/react-query": "5.90.9",
13
+ "@testing-library/react": "^16.3.0",
14
+ "@types/react": "^19.2.5",
15
+ "@vitejs/plugin-react": "^5.1.1",
16
+ "@vitest/coverage-v8": "^4.0.9",
17
+ "jsdom": "^27.2.0",
18
+ "react-dom": "19.2.0",
19
+ "typescript": "5.9.3",
20
+ "vitest": "4.0.9"
21
+ },
22
+ "module": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "require": "./dist/index.cjs"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md"
34
+ ],
35
+ "description": "Personal Finance Management - ACCOUNTS feature layer",
36
+ "keywords": [
37
+ "pfm",
38
+ "finance",
39
+ "accounts",
40
+ "feature",
41
+ "react",
42
+ "typescript"
43
+ ],
44
+ "author": "Lenny Miller",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/lennylmiller/pfm-research",
49
+ "directory": "packages/accounts/feature"
50
+ },
51
+ "bugs": "https://github.com/lennylmiller/pfm-research/issues",
52
+ "homepage": "https://github.com/lennylmiller/pfm-research#readme",
53
+ "scripts": {
54
+ "test": "vitest run",
55
+ "test:watch": "vitest",
56
+ "test:ui": "vitest --ui",
57
+ "test:coverage": "vitest run --coverage",
58
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean"
59
+ }
60
+ }