@pfm-platform/transactions-feature 0.1.1 → 0.2.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 CHANGED
@@ -44,21 +44,21 @@ function useFilteredTransactions(options) {
44
44
  );
45
45
  }
46
46
  if (accountIds.length > 0) {
47
- filtered = filtered.filter((t) => accountIds.includes(String(t.links?.account)));
47
+ filtered = filtered.filter((t) => accountIds.includes(t.account_id));
48
48
  }
49
49
  if (tagNames.length > 0) {
50
50
  if (excludeTags) {
51
51
  filtered = filtered.filter(
52
- (t) => !t.tags?.some((tag) => tagNames.includes(tag.name))
52
+ (t) => !t.transaction_tags?.some((tag) => tagNames.includes(tag.tag_name))
53
53
  );
54
54
  } else {
55
55
  filtered = filtered.filter(
56
- (t) => t.tags?.some((tag) => tagNames.includes(tag.name))
56
+ (t) => t.transaction_tags?.some((tag) => tagNames.includes(tag.tag_name))
57
57
  );
58
58
  }
59
59
  }
60
60
  if (showUntagged) {
61
- filtered = filtered.filter((t) => !t.tags || t.tags.length === 0);
61
+ filtered = filtered.filter((t) => !t.transaction_tags || t.transaction_tags.length === 0);
62
62
  }
63
63
  return filtered;
64
64
  }, [data, type, name, accountIds, tagNames, showUntagged, excludeTags]);
@@ -75,8 +75,8 @@ function useTransactionSummary(options) {
75
75
  }
76
76
  const credits = data.transactions.filter((t) => t.transaction_type === "Credit");
77
77
  const debits = data.transactions.filter((t) => t.transaction_type === "Debit");
78
- const creditAmount = credits.reduce((sum, t) => sum + (t.balance || 0), 0);
79
- const debitAmount = debits.reduce((sum, t) => sum + (t.balance || 0), 0);
78
+ const creditAmount = credits.reduce((sum, t) => sum + (t.amount || 0), 0);
79
+ const debitAmount = debits.reduce((sum, t) => sum + (t.amount || 0), 0);
80
80
  return {
81
81
  totalCount: data.transactions.length,
82
82
  totalAmount: creditAmount - debitAmount,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useTransactionFilters.ts","../src/hooks/useFilteredTransactions.ts","../src/hooks/useTransactionSummary.ts","../src/hooks/useTransactionTagging.ts"],"names":["useMemo","useTransactions"],"mappings":";;;;;;AAgCO,SAAS,qBAAA,CACd,OAAA,GAAoC,EAAC,EACb;AACxB,EAAA,OAAOA,cAAQ,MAAM;AACnB,IAAA,MAAM,EAAE,MAAM,QAAA,GAAW,IAAI,IAAA,GAAO,EAAC,EAAG,IAAA,EAAK,GAAI,OAAA;AAEjD,IAAA,MAAM,gBAAgB,CAAC,CAAC,QAAQ,IAAA,CAAK,IAAA,GAAO,MAAA,GAAS,CAAA;AACrD,IAAA,MAAM,iBAAA,GAAoB,SAAS,MAAA,GAAS,CAAA;AAC5C,IAAA,MAAM,aAAA,GAAgB,KAAK,MAAA,GAAS,CAAA;AACpC,IAAA,MAAM,gBAAgB,CAAC,CAAC,QAAQ,IAAA,CAAK,IAAA,GAAO,MAAA,GAAS,CAAA;AAErD,IAAA,OAAO;AAAA,MACL,aAAA;AAAA,MACA,iBAAA;AAAA,MACA,aAAA;AAAA,MACA,aAAA;AAAA,MACA,gBAAA,EACE,aAAA,IAAiB,iBAAA,IAAqB,aAAA,IAAiB;AAAA,KAC3D;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,UAAU,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,IAAI,CAAC,CAAA;AACjE;ACtBO,SAAS,wBAAwB,OAAA,EAAwB;AAC9D,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM,UAAA,GAAa,EAAC,EAAG,QAAA,GAAW,EAAC,EAAG,YAAA,EAAc,WAAA,EAAY,GAAI,OAAA;AAE1F,EAAA,MAAM,EAAE,IAAA,EAAK,GAAIC,sCAAA,CAAgB;AAAA,IAC/B,MAAA;AAAA,IACA,OAAA,EAAS;AAAA,MACP,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,QAAQ,OAAA,CAAQ;AAAA;AAClB,GACD,CAAA;AAED,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,IAAA,EAAM,YAAA,IAAgB,IAAA,CAAK,YAAA,CAAa,WAAW,CAAA,EAAG;AACzD,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,IAAI,WAAW,IAAA,CAAK,YAAA;AAGpB,IAAA,IAAI,IAAA,IAAQ,IAAA,CAAK,IAAA,EAAK,CAAE,SAAS,CAAA,EAAG;AAClC,MAAA,QAAA,GAAW,SAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,qBAAqB,IAAI,CAAA;AAAA,IAC/D;AAGA,IAAA,IAAI,IAAA,IAAQ,IAAA,CAAK,IAAA,EAAK,CAAE,SAAS,CAAA,EAAG;AAClC,MAAA,MAAM,UAAA,GAAa,KAAK,WAAA,EAAY;AACpC,MAAA,QAAA,GAAW,QAAA,CAAS,MAAA;AAAA,QAAO,CAAC,CAAA,KAC1B,CAAA,CAAE,QAAA,EAAU,aAAY,CAAE,QAAA,CAAS,UAAU,CAAA,IAC7C,CAAA,CAAE,aAAA,EAAe,WAAA,EAAY,CAAE,SAAS,UAAU;AAAA,OACpD;AAAA,IACF;AAGA,IAAA,IAAI,UAAA,CAAW,SAAS,CAAA,EAAG;AACzB,MAAA,QAAA,GAAW,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,UAAA,CAAW,QAAA,CAAS,MAAA,CAAO,CAAA,CAAE,KAAA,EAAO,OAAO,CAAC,CAAC,CAAA;AAAA,IACjF;AAGA,IAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,MAAA,IAAI,WAAA,EAAa;AAEf,QAAA,QAAA,GAAW,QAAA,CAAS,MAAA;AAAA,UAAO,CAAC,CAAA,KAC1B,CAAC,CAAA,CAAE,IAAA,EAAM,IAAA,CAAK,CAAC,GAAA,KAAQ,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,IAAI,CAAC;AAAA,SACpD;AAAA,MACF,CAAA,MAAO;AAEL,QAAA,QAAA,GAAW,QAAA,CAAS,MAAA;AAAA,UAAO,CAAC,CAAA,KAC1B,CAAA,CAAE,IAAA,EAAM,IAAA,CAAK,CAAC,GAAA,KAAQ,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,IAAI,CAAC;AAAA,SACnD;AAAA,MACF;AAAA,IACF;AAGA,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,QAAA,GAAW,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,EAAE,IAAA,IAAQ,CAAA,CAAE,IAAA,CAAK,MAAA,KAAW,CAAC,CAAA;AAAA,IAClE;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA,EAAG,CAAC,IAAA,EAAM,IAAA,EAAM,MAAM,UAAA,EAAY,QAAA,EAAU,YAAA,EAAc,WAAW,CAAC,CAAA;AACxE;AC1DO,SAAS,sBAAsB,OAAA,EAAoD;AACxF,EAAA,MAAM,EAAE,MAAA,EAAQ,QAAA,EAAU,MAAA,EAAO,GAAI,OAAA;AAErC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAIC,sCAAAA,CAAgB;AAAA,IAC/B,MAAA;AAAA,IACA,OAAA,EAAS,EAAE,QAAA,EAAU,MAAA;AAAO,GAC7B,CAAA;AAED,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAM,YAAA,EAAc;AACvB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,OAAA,GAAU,KAAK,YAAA,CAAa,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,qBAAqB,QAAQ,CAAA;AAC/E,IAAA,MAAM,MAAA,GAAS,KAAK,YAAA,CAAa,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,qBAAqB,OAAO,CAAA;AAE7E,IAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,MAAA,CAAO,CAAC,GAAA,EAAK,MAAM,GAAA,IAAO,CAAA,CAAE,OAAA,IAAW,CAAA,CAAA,EAAI,CAAC,CAAA;AACzE,IAAA,MAAM,WAAA,GAAc,MAAA,CAAO,MAAA,CAAO,CAAC,GAAA,EAAK,MAAM,GAAA,IAAO,CAAA,CAAE,OAAA,IAAW,CAAA,CAAA,EAAI,CAAC,CAAA;AAEvE,IAAA,OAAO;AAAA,MACL,UAAA,EAAY,KAAK,YAAA,CAAa,MAAA;AAAA,MAC9B,aAAa,YAAA,GAAe,WAAA;AAAA,MAC5B,aAAa,OAAA,CAAQ,MAAA;AAAA,MACrB,YAAA;AAAA,MACA,YAAY,MAAA,CAAO,MAAA;AAAA,MACnB,WAAA;AAAA,MACA,eAAA,EAAiB,IAAA,CAAK,YAAA,CAAa,MAAA,GAAS;AAAA,KAC9C;AAAA,EACF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AACX;ACZO,SAAS,qBAAA,GAAwB;AAItC,EAAA,MAAM,mBAAA,GAAsBA,aAAAA;AAAA,IAC1B,MAAM,CAAC,OAAA,KAAwC;AAC7C,MAAA,IAAI,OAAA,CAAQ,SAAS,OAAA,EAAS;AAC5B,QAAA,OAAO,CAAA;AAAA,MACT;AACA,MAAA,OAAO,OAAA,CAAQ,MAAM,MAAA,CAAO,CAAC,KAAK,IAAA,KAAS,GAAA,GAAM,IAAA,CAAK,KAAA,EAAO,CAAC,CAAA;AAAA,IAChE,CAAA;AAAA,IACA;AAAC,GACH;AAOA,EAAA,MAAM,eAAA,GAAkBA,aAAAA;AAAA,IACtB,MACE,CAAC,OAAA,EAA6B,iBAAA,KAAkD;AAC9E,MAAA,MAAM,SAAmB,EAAC;AAE1B,MAAA,IAAI,OAAA,CAAQ,SAAS,SAAA,EAAW;AAE9B,QAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,IAAW,OAAA,CAAQ,OAAA,CAAQ,WAAW,CAAA,EAAG;AACpD,UAAA,MAAA,CAAO,KAAK,8BAA8B,CAAA;AAAA,QAC5C;AAGA,QAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,CAAC,GAAA,KAAQ,CAAC,GAAA,IAAO,GAAA,CAAI,IAAA,EAAK,CAAE,MAAA,KAAW,CAAC,CAAA;AACjF,QAAA,IAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AACxB,UAAA,MAAA,CAAO,KAAK,sBAAsB,CAAA;AAAA,QACpC;AAEA,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,OAAO,MAAA,KAAW,CAAA;AAAA,UAC3B;AAAA,SACF;AAAA,MACF,CAAA,MAAO;AAEL,QAAA,IAAI,CAAC,OAAA,CAAQ,KAAA,IAAS,OAAA,CAAQ,KAAA,CAAM,WAAW,CAAA,EAAG;AAChD,UAAA,MAAA,CAAO,KAAK,qCAAqC,CAAA;AAAA,QACnD;AAGA,QAAA,MAAM,YAAA,GAAe,QAAQ,KAAA,CAAM,MAAA;AAAA,UACjC,CAAC,IAAA,KAAS,CAAC,IAAA,CAAK,IAAA,IAAQ,IAAA,CAAK,IAAA,CAAK,IAAA,EAAK,CAAE,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,KAAA,IAAS;AAAA,SACzE;AACA,QAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,UAAA,MAAA,CAAO,KAAK,kDAAkD,CAAA;AAAA,QAChE;AAEA,QAAA,MAAM,gBAAA,GAAmB,oBAAoB,OAAO,CAAA;AAGpD,QAAA,IAAI,eAAA,GAAuC,MAAA;AAC3C,QAAA,IAAI,sBAAsB,MAAA,EAAW;AAEnC,UAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,gBAAA,GAAmB,iBAAiB,CAAA;AAChE,UAAA,eAAA,GAAkB,UAAA,GAAa,IAAA;AAE/B,UAAA,IAAI,CAAC,eAAA,EAAiB;AACpB,YAAA,MAAA,CAAO,IAAA;AAAA,cACL,CAAA,gBAAA,EAAmB,iBAAiB,OAAA,CAAQ,CAAC,CAAC,CAAA,mCAAA,EAAsC,iBAAA,CAAkB,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA;AAAA,aAClH;AAAA,UACF;AAAA,QACF;AAEA,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,OAAO,MAAA,KAAW,CAAA;AAAA,UAC3B,MAAA;AAAA,UACA,gBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACF,CAAC,mBAAmB;AAAA,GACtB;AAMA,EAAA,MAAM,aAAA,GAAgBA,aAAAA;AAAA,IACpB,MACE,CAAC,iBAAA,EAA2B,WAAA,EAAuB,KAAA,KAAuC;AACxF,MAAA,IAAI,WAAA,CAAY,MAAA,KAAW,KAAA,CAAM,MAAA,EAAQ;AACvC,QAAA,MAAM,IAAI,MAAM,oDAAoD,CAAA;AAAA,MACtE;AAEA,MAAA,MAAM,eAAA,GAAkB,YAAY,MAAA,CAAO,CAAC,KAAK,CAAA,KAAM,GAAA,GAAM,GAAG,CAAC,CAAA;AACjE,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,eAAA,GAAkB,GAAG,IAAI,IAAA,EAAM;AAC1C,QAAA,MAAM,IAAI,MAAM,6BAA6B,CAAA;AAAA,MAC/C;AAEA,MAAA,OAAO,WAAA,CAAY,GAAA,CAAI,CAAC,UAAA,EAAY,KAAA,MAAW;AAAA,QAC7C,IAAA,EAAM,MAAM,KAAK,CAAA;AAAA,QACjB,eAAA,EAAkB,oBAAoB,UAAA,GAAc;AAAA,OACtD,CAAE,CAAA;AAAA,IACJ,CAAA;AAAA,IACF;AAAC,GACH;AAKA,EAAA,MAAM,aAAA,GAAgBA,aAAAA;AAAA,IACpB,MAAM,CAAC,IAAA,KAA6B;AAClC,MAAA,OAAO,IAAA,CACJ,GAAA,CAAI,CAAC,GAAA,KAAQ,IAAI,IAAA,EAAK,CAAE,WAAA,EAAa,EACrC,MAAA,CAAO,CAAC,GAAA,KAAQ,GAAA,CAAI,SAAS,CAAC,CAAA;AAAA,IACnC,CAAA;AAAA,IACA;AAAC,GACH;AAEA,EAAA,OAAO;AAAA,IACL,mBAAA;AAAA,IACA,eAAA;AAAA,IACA,aAAA;AAAA,IACA;AAAA,GACF;AACF","file":"index.cjs","sourcesContent":["import { useMemo } from 'react';\n\nexport interface TransactionFilterState {\n hasNameFilter: boolean;\n hasAccountsFilter: boolean;\n hasTagsFilter: boolean;\n hasTypeFilter: boolean;\n hasSearchFilters: boolean;\n}\n\nexport interface TransactionFilterOptions {\n name?: string;\n accounts?: string[];\n tags?: string[];\n type?: string;\n}\n\n/**\n * Check which transaction filters are active\n *\n * Replaces: Filters.hasNameFilter, hasAccountsFilter, hasTagsFilter, hasTypeFilter, hasSearchFilters\n *\n * Business logic:\n * - hasNameFilter: name is not empty\n * - hasAccountsFilter: accounts array has items\n * - hasTagsFilter: tags array has items\n * - hasTypeFilter: type is not empty\n * - hasSearchFilters: any of the above filters are active\n *\n * @param options - Filter options (name, accounts, tags, type)\n * @returns Object with boolean flags for each filter type\n */\nexport function useTransactionFilters(\n options: TransactionFilterOptions = {}\n): TransactionFilterState {\n return useMemo(() => {\n const { name, accounts = [], tags = [], type } = options;\n\n const hasNameFilter = !!name && name.trim().length > 0;\n const hasAccountsFilter = accounts.length > 0;\n const hasTagsFilter = tags.length > 0;\n const hasTypeFilter = !!type && type.trim().length > 0;\n\n return {\n hasNameFilter,\n hasAccountsFilter,\n hasTagsFilter,\n hasTypeFilter,\n hasSearchFilters:\n hasNameFilter || hasAccountsFilter || hasTagsFilter || hasTypeFilter,\n };\n }, [options.name, options.accounts, options.tags, options.type]);\n}\n","import { useMemo } from 'react';\nimport { useTransactions } from '@pfm-platform/transactions-data-access';\n\nexport interface FilterOptions {\n userId: string;\n type?: string;\n name?: string;\n accountIds?: string[];\n tagNames?: string[];\n showUntagged?: boolean;\n excludeTags?: boolean;\n begin_on?: string;\n end_on?: string;\n}\n\n/**\n * Filter transactions by various criteria\n *\n * Replaces: LocalSearch.transactionsByType and filter logic\n *\n * Business logic:\n * - Filter by transaction_type if specified\n * - Filter by name (case-insensitive substring match)\n * - Filter by account IDs\n * - Filter by tag names (or untagged)\n * - Support exclude tags mode\n *\n * @param options - Filter criteria\n * @returns Filtered transactions array\n */\nexport function useFilteredTransactions(options: FilterOptions) {\n const { userId, type, name, accountIds = [], tagNames = [], showUntagged, excludeTags } = options;\n\n const { data } = useTransactions({\n userId,\n filters: {\n begin_on: options.begin_on,\n end_on: options.end_on,\n },\n });\n\n return useMemo(() => {\n if (!data?.transactions || data.transactions.length === 0) {\n return [];\n }\n\n let filtered = data.transactions;\n\n // Filter by type\n if (type && type.trim().length > 0) {\n filtered = filtered.filter((t) => t.transaction_type === type);\n }\n\n // Filter by name (case-insensitive substring)\n if (name && name.trim().length > 0) {\n const searchTerm = name.toLowerCase();\n filtered = filtered.filter((t) =>\n t.nickname?.toLowerCase().includes(searchTerm) ||\n t.original_name?.toLowerCase().includes(searchTerm)\n );\n }\n\n // Filter by account IDs\n if (accountIds.length > 0) {\n filtered = filtered.filter((t) => accountIds.includes(String(t.links?.account)));\n }\n\n // Filter by tags\n if (tagNames.length > 0) {\n if (excludeTags) {\n // Exclude transactions with these tags\n filtered = filtered.filter((t) =>\n !t.tags?.some((tag) => tagNames.includes(tag.name))\n );\n } else {\n // Include only transactions with these tags\n filtered = filtered.filter((t) =>\n t.tags?.some((tag) => tagNames.includes(tag.name))\n );\n }\n }\n\n // Filter for untagged\n if (showUntagged) {\n filtered = filtered.filter((t) => !t.tags || t.tags.length === 0);\n }\n\n return filtered;\n }, [data, type, name, accountIds, tagNames, showUntagged, excludeTags]);\n}\n","import { useMemo } from 'react';\nimport { useTransactions } from '@pfm-platform/transactions-data-access';\n\nexport interface TransactionSummary {\n totalCount: number;\n totalAmount: number;\n creditCount: number;\n creditAmount: number;\n debitCount: number;\n debitAmount: number;\n hasTransactions: boolean;\n}\n\nexport interface SummaryOptions {\n userId: string;\n begin_on?: string;\n end_on?: string;\n}\n\n/**\n * Calculate transaction summary statistics\n *\n * Business logic:\n * - Count total transactions\n * - Calculate total amount (credits - debits)\n * - Separate credit/debit counts and amounts\n * - Provide hasTransactions boolean\n *\n * @param options - User ID and date range\n * @returns Summary with counts, amounts, and hasTransactions flag\n */\nexport function useTransactionSummary(options: SummaryOptions): TransactionSummary | null {\n const { userId, begin_on, end_on } = options;\n\n const { data } = useTransactions({\n userId,\n filters: { begin_on, end_on },\n });\n\n return useMemo(() => {\n if (!data?.transactions) {\n return null;\n }\n\n const credits = data.transactions.filter((t) => t.transaction_type === 'Credit');\n const debits = data.transactions.filter((t) => t.transaction_type === 'Debit');\n\n const creditAmount = credits.reduce((sum, t) => sum + (t.balance || 0), 0);\n const debitAmount = debits.reduce((sum, t) => sum + (t.balance || 0), 0);\n\n return {\n totalCount: data.transactions.length,\n totalAmount: creditAmount - debitAmount,\n creditCount: credits.length,\n creditAmount,\n debitCount: debits.length,\n debitAmount,\n hasTransactions: data.transactions.length > 0,\n };\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport type { TransactionTagging } from '@pfm-platform/shared';\n\nexport interface TaggingValidation {\n isValid: boolean;\n errors: string[];\n totalSplitAmount?: number;\n splitSumMatches?: boolean;\n}\n\nexport interface SplitSuggestion {\n name: string;\n suggestedAmount: number;\n}\n\n/**\n * Business logic helpers for transaction tagging\n *\n * Provides:\n * - Validation for split tagging (amounts must sum to transaction total)\n * - Split suggestions based on percentages\n * - Tag normalization (trim, lowercase)\n *\n * @example Regular Tagging\n * ```tsx\n * const { validateTagging } = useTransactionTagging();\n * const tagging: TransactionTagging = {\n * type: 'regular',\n * repeat: true,\n * regular: ['groceries', 'shopping']\n * };\n * const validation = validateTagging(tagging);\n * ```\n *\n * @example Split Tagging\n * ```tsx\n * const { validateTagging, calculateSplitTotal } = useTransactionTagging();\n * const tagging: TransactionTagging = {\n * type: 'split',\n * split: [\n * { name: 'groceries', value: 45.50 },\n * { name: 'household', value: 25.25 }\n * ]\n * };\n * const total = calculateSplitTotal(tagging);\n * const validation = validateTagging(tagging, 70.75); // transaction amount\n * ```\n */\nexport function useTransactionTagging() {\n /**\n * Calculate total amount from split tagging\n */\n const calculateSplitTotal = useMemo(\n () => (tagging: TransactionTagging): number => {\n if (tagging.type !== 'split') {\n return 0;\n }\n return tagging.split.reduce((sum, item) => sum + item.value, 0);\n },\n []\n );\n\n /**\n * Validate tagging data\n * For split mode: checks if amounts sum to transaction total\n * For regular mode: checks if tags array is not empty\n */\n const validateTagging = useMemo(\n () =>\n (tagging: TransactionTagging, transactionAmount?: number): TaggingValidation => {\n const errors: string[] = [];\n\n if (tagging.type === 'regular') {\n // Regular mode validation\n if (!tagging.regular || tagging.regular.length === 0) {\n errors.push('At least one tag is required');\n }\n\n // Check for empty tag names\n const emptyTags = tagging.regular.filter((tag) => !tag || tag.trim().length === 0);\n if (emptyTags.length > 0) {\n errors.push('Tags cannot be empty');\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n };\n } else {\n // Split mode validation\n if (!tagging.split || tagging.split.length === 0) {\n errors.push('At least one split item is required');\n }\n\n // Check for empty names or invalid amounts\n const invalidItems = tagging.split.filter(\n (item) => !item.name || item.name.trim().length === 0 || item.value <= 0\n );\n if (invalidItems.length > 0) {\n errors.push('Split items must have names and positive amounts');\n }\n\n const totalSplitAmount = calculateSplitTotal(tagging);\n\n // If transaction amount is provided, check if split amounts sum to transaction total\n let splitSumMatches: boolean | undefined = undefined;\n if (transactionAmount !== undefined) {\n // Allow small floating point difference (0.01)\n const difference = Math.abs(totalSplitAmount - transactionAmount);\n splitSumMatches = difference < 0.01;\n\n if (!splitSumMatches) {\n errors.push(\n `Split amounts ($${totalSplitAmount.toFixed(2)}) must sum to transaction amount ($${transactionAmount.toFixed(2)})`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n totalSplitAmount,\n splitSumMatches,\n };\n }\n },\n [calculateSplitTotal]\n );\n\n /**\n * Suggest split amounts based on percentages\n * Useful for common splits like 50/50, 60/40, etc.\n */\n const suggestSplits = useMemo(\n () =>\n (transactionAmount: number, percentages: number[], names: string[]): SplitSuggestion[] => {\n if (percentages.length !== names.length) {\n throw new Error('Percentages and names arrays must have same length');\n }\n\n const totalPercentage = percentages.reduce((sum, p) => sum + p, 0);\n if (Math.abs(totalPercentage - 100) > 0.01) {\n throw new Error('Percentages must sum to 100');\n }\n\n return percentages.map((percentage, index) => ({\n name: names[index],\n suggestedAmount: (transactionAmount * percentage) / 100,\n }));\n },\n []\n );\n\n /**\n * Normalize tag names (trim whitespace, lowercase)\n */\n const normalizeTags = useMemo(\n () => (tags: string[]): string[] => {\n return tags\n .map((tag) => tag.trim().toLowerCase())\n .filter((tag) => tag.length > 0);\n },\n []\n );\n\n return {\n calculateSplitTotal,\n validateTagging,\n suggestSplits,\n normalizeTags,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/useTransactionFilters.ts","../src/hooks/useFilteredTransactions.ts","../src/hooks/useTransactionSummary.ts","../src/hooks/useTransactionTagging.ts"],"names":["useMemo","useTransactions"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useMemo } from 'react';\n\nexport interface TransactionFilterState {\n hasNameFilter: boolean;\n hasAccountsFilter: boolean;\n hasTagsFilter: boolean;\n hasTypeFilter: boolean;\n hasSearchFilters: boolean;\n}\n\nexport interface TransactionFilterOptions {\n name?: string;\n accounts?: string[];\n tags?: string[];\n type?: string;\n}\n\n/**\n * Check which transaction filters are active\n *\n * Replaces: Filters.hasNameFilter, hasAccountsFilter, hasTagsFilter, hasTypeFilter, hasSearchFilters\n *\n * Business logic:\n * - hasNameFilter: name is not empty\n * - hasAccountsFilter: accounts array has items\n * - hasTagsFilter: tags array has items\n * - hasTypeFilter: type is not empty\n * - hasSearchFilters: any of the above filters are active\n *\n * @param options - Filter options (name, accounts, tags, type)\n * @returns Object with boolean flags for each filter type\n */\nexport function useTransactionFilters(\n options: TransactionFilterOptions = {}\n): TransactionFilterState {\n return useMemo(() => {\n const { name, accounts = [], tags = [], type } = options;\n\n const hasNameFilter = !!name && name.trim().length > 0;\n const hasAccountsFilter = accounts.length > 0;\n const hasTagsFilter = tags.length > 0;\n const hasTypeFilter = !!type && type.trim().length > 0;\n\n return {\n hasNameFilter,\n hasAccountsFilter,\n hasTagsFilter,\n hasTypeFilter,\n hasSearchFilters:\n hasNameFilter || hasAccountsFilter || hasTagsFilter || hasTypeFilter,\n };\n }, [options.name, options.accounts, options.tags, options.type]);\n}\n","import { useMemo } from 'react';\nimport { useTransactions } from '@pfm-platform/transactions-data-access';\n\nexport interface FilterOptions {\n userId: string;\n type?: string;\n name?: string;\n accountIds?: string[];\n tagNames?: string[];\n showUntagged?: boolean;\n excludeTags?: boolean;\n begin_on?: string;\n end_on?: string;\n}\n\n/**\n * Filter transactions by various criteria\n *\n * Replaces: LocalSearch.transactionsByType and filter logic\n *\n * Business logic:\n * - Filter by transaction_type if specified\n * - Filter by name (case-insensitive substring match)\n * - Filter by account IDs\n * - Filter by tag names (or untagged)\n * - Support exclude tags mode\n *\n * @param options - Filter criteria\n * @returns Filtered transactions array\n */\nexport function useFilteredTransactions(options: FilterOptions) {\n const { userId, type, name, accountIds = [], tagNames = [], showUntagged, excludeTags } = options;\n\n const { data } = useTransactions({\n userId,\n filters: {\n begin_on: options.begin_on,\n end_on: options.end_on,\n },\n });\n\n return useMemo(() => {\n if (!data?.transactions || data.transactions.length === 0) {\n return [];\n }\n\n let filtered = data.transactions;\n\n // Filter by type\n if (type && type.trim().length > 0) {\n filtered = filtered.filter((t) => t.transaction_type === type);\n }\n\n // Filter by name (case-insensitive substring)\n if (name && name.trim().length > 0) {\n const searchTerm = name.toLowerCase();\n filtered = filtered.filter((t) =>\n t.nickname?.toLowerCase().includes(searchTerm) ||\n t.original_name?.toLowerCase().includes(searchTerm)\n );\n }\n\n // Filter by account IDs\n if (accountIds.length > 0) {\n filtered = filtered.filter((t) => accountIds.includes(t.account_id));\n }\n\n // Filter by tags\n if (tagNames.length > 0) {\n if (excludeTags) {\n // Exclude transactions with these tags\n filtered = filtered.filter((t) =>\n !t.transaction_tags?.some((tag) => tagNames.includes(tag.tag_name))\n );\n } else {\n // Include only transactions with these tags\n filtered = filtered.filter((t) =>\n t.transaction_tags?.some((tag) => tagNames.includes(tag.tag_name))\n );\n }\n }\n\n // Filter for untagged\n if (showUntagged) {\n filtered = filtered.filter((t) => !t.transaction_tags || t.transaction_tags.length === 0);\n }\n\n return filtered;\n }, [data, type, name, accountIds, tagNames, showUntagged, excludeTags]);\n}\n","import { useMemo } from 'react';\nimport { useTransactions } from '@pfm-platform/transactions-data-access';\n\nexport interface TransactionSummary {\n totalCount: number;\n totalAmount: number;\n creditCount: number;\n creditAmount: number;\n debitCount: number;\n debitAmount: number;\n hasTransactions: boolean;\n}\n\nexport interface SummaryOptions {\n userId: string;\n begin_on?: string;\n end_on?: string;\n}\n\n/**\n * Calculate transaction summary statistics\n *\n * Business logic:\n * - Count total transactions\n * - Calculate total amount (credits - debits)\n * - Separate credit/debit counts and amounts\n * - Provide hasTransactions boolean\n *\n * @param options - User ID and date range\n * @returns Summary with counts, amounts, and hasTransactions flag\n */\nexport function useTransactionSummary(options: SummaryOptions): TransactionSummary | null {\n const { userId, begin_on, end_on } = options;\n\n const { data } = useTransactions({\n userId,\n filters: { begin_on, end_on },\n });\n\n return useMemo(() => {\n if (!data?.transactions) {\n return null;\n }\n\n const credits = data.transactions.filter((t) => t.transaction_type === 'Credit');\n const debits = data.transactions.filter((t) => t.transaction_type === 'Debit');\n\n const creditAmount = credits.reduce((sum, t) => sum + (t.amount || 0), 0);\n const debitAmount = debits.reduce((sum, t) => sum + (t.amount || 0), 0);\n\n return {\n totalCount: data.transactions.length,\n totalAmount: creditAmount - debitAmount,\n creditCount: credits.length,\n creditAmount,\n debitCount: debits.length,\n debitAmount,\n hasTransactions: data.transactions.length > 0,\n };\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport type { TransactionTagging } from '@pfm-platform/shared';\n\nexport interface TaggingValidation {\n isValid: boolean;\n errors: string[];\n totalSplitAmount?: number;\n splitSumMatches?: boolean;\n}\n\nexport interface SplitSuggestion {\n name: string;\n suggestedAmount: number;\n}\n\n/**\n * Business logic helpers for transaction tagging\n *\n * Provides:\n * - Validation for split tagging (amounts must sum to transaction total)\n * - Split suggestions based on percentages\n * - Tag normalization (trim, lowercase)\n *\n * @example Regular Tagging\n * ```tsx\n * const { validateTagging } = useTransactionTagging();\n * const tagging: TransactionTagging = {\n * type: 'regular',\n * repeat: true,\n * regular: ['groceries', 'shopping']\n * };\n * const validation = validateTagging(tagging);\n * ```\n *\n * @example Split Tagging\n * ```tsx\n * const { validateTagging, calculateSplitTotal } = useTransactionTagging();\n * const tagging: TransactionTagging = {\n * type: 'split',\n * split: [\n * { name: 'groceries', value: 45.50 },\n * { name: 'household', value: 25.25 }\n * ]\n * };\n * const total = calculateSplitTotal(tagging);\n * const validation = validateTagging(tagging, 70.75); // transaction amount\n * ```\n */\nexport function useTransactionTagging() {\n /**\n * Calculate total amount from split tagging\n */\n const calculateSplitTotal = useMemo(\n () => (tagging: TransactionTagging): number => {\n if (tagging.type !== 'split') {\n return 0;\n }\n return tagging.split.reduce((sum, item) => sum + item.value, 0);\n },\n []\n );\n\n /**\n * Validate tagging data\n * For split mode: checks if amounts sum to transaction total\n * For regular mode: checks if tags array is not empty\n */\n const validateTagging = useMemo(\n () =>\n (tagging: TransactionTagging, transactionAmount?: number): TaggingValidation => {\n const errors: string[] = [];\n\n if (tagging.type === 'regular') {\n // Regular mode validation\n if (!tagging.regular || tagging.regular.length === 0) {\n errors.push('At least one tag is required');\n }\n\n // Check for empty tag names\n const emptyTags = tagging.regular.filter((tag) => !tag || tag.trim().length === 0);\n if (emptyTags.length > 0) {\n errors.push('Tags cannot be empty');\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n };\n } else {\n // Split mode validation\n if (!tagging.split || tagging.split.length === 0) {\n errors.push('At least one split item is required');\n }\n\n // Check for empty names or invalid amounts\n const invalidItems = tagging.split.filter(\n (item) => !item.name || item.name.trim().length === 0 || item.value <= 0\n );\n if (invalidItems.length > 0) {\n errors.push('Split items must have names and positive amounts');\n }\n\n const totalSplitAmount = calculateSplitTotal(tagging);\n\n // If transaction amount is provided, check if split amounts sum to transaction total\n let splitSumMatches: boolean | undefined = undefined;\n if (transactionAmount !== undefined) {\n // Allow small floating point difference (0.01)\n const difference = Math.abs(totalSplitAmount - transactionAmount);\n splitSumMatches = difference < 0.01;\n\n if (!splitSumMatches) {\n errors.push(\n `Split amounts ($${totalSplitAmount.toFixed(2)}) must sum to transaction amount ($${transactionAmount.toFixed(2)})`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n totalSplitAmount,\n splitSumMatches,\n };\n }\n },\n [calculateSplitTotal]\n );\n\n /**\n * Suggest split amounts based on percentages\n * Useful for common splits like 50/50, 60/40, etc.\n */\n const suggestSplits = useMemo(\n () =>\n (transactionAmount: number, percentages: number[], names: string[]): SplitSuggestion[] => {\n if (percentages.length !== names.length) {\n throw new Error('Percentages and names arrays must have same length');\n }\n\n const totalPercentage = percentages.reduce((sum, p) => sum + p, 0);\n if (Math.abs(totalPercentage - 100) > 0.01) {\n throw new Error('Percentages must sum to 100');\n }\n\n return percentages.map((percentage, index) => ({\n name: names[index],\n suggestedAmount: (transactionAmount * percentage) / 100,\n }));\n },\n []\n );\n\n /**\n * Normalize tag names (trim whitespace, lowercase)\n */\n const normalizeTags = useMemo(\n () => (tags: string[]): string[] => {\n return tags\n .map((tag) => tag.trim().toLowerCase())\n .filter((tag) => tag.length > 0);\n },\n []\n );\n\n return {\n calculateSplitTotal,\n validateTagging,\n suggestSplits,\n normalizeTags,\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -58,23 +58,27 @@ interface FilterOptions {
58
58
  */
59
59
  declare function useFilteredTransactions(options: FilterOptions): {
60
60
  id: string;
61
+ user_id: string;
62
+ account_id: string;
61
63
  reference_id: string;
62
64
  transaction_type: "Debit" | "Credit";
63
- balance: number;
65
+ amount: number;
64
66
  posted_at: string;
65
- created_at: string;
66
67
  nickname: string;
67
68
  original_name: string;
68
- tags: {
69
- name: string;
70
- balance: number;
69
+ transaction_tags: {
70
+ id: string;
71
+ transaction_id: string;
72
+ tag_name: string;
73
+ amount: number | null;
74
+ created_at?: string | null | undefined;
71
75
  }[];
72
- links: {
73
- account: number;
74
- };
76
+ legacy_id?: string | null | undefined;
75
77
  memo?: string | null | undefined;
76
- deleted_at?: string | null | undefined;
77
78
  check_number?: string | null | undefined;
79
+ deleted_at?: string | null | undefined;
80
+ created_at?: string | null | undefined;
81
+ updated_at?: string | null | undefined;
78
82
  }[];
79
83
 
80
84
  interface TransactionSummary {
package/dist/index.d.ts CHANGED
@@ -58,23 +58,27 @@ interface FilterOptions {
58
58
  */
59
59
  declare function useFilteredTransactions(options: FilterOptions): {
60
60
  id: string;
61
+ user_id: string;
62
+ account_id: string;
61
63
  reference_id: string;
62
64
  transaction_type: "Debit" | "Credit";
63
- balance: number;
65
+ amount: number;
64
66
  posted_at: string;
65
- created_at: string;
66
67
  nickname: string;
67
68
  original_name: string;
68
- tags: {
69
- name: string;
70
- balance: number;
69
+ transaction_tags: {
70
+ id: string;
71
+ transaction_id: string;
72
+ tag_name: string;
73
+ amount: number | null;
74
+ created_at?: string | null | undefined;
71
75
  }[];
72
- links: {
73
- account: number;
74
- };
76
+ legacy_id?: string | null | undefined;
75
77
  memo?: string | null | undefined;
76
- deleted_at?: string | null | undefined;
77
78
  check_number?: string | null | undefined;
79
+ deleted_at?: string | null | undefined;
80
+ created_at?: string | null | undefined;
81
+ updated_at?: string | null | undefined;
78
82
  }[];
79
83
 
80
84
  interface TransactionSummary {
package/dist/index.js CHANGED
@@ -42,21 +42,21 @@ function useFilteredTransactions(options) {
42
42
  );
43
43
  }
44
44
  if (accountIds.length > 0) {
45
- filtered = filtered.filter((t) => accountIds.includes(String(t.links?.account)));
45
+ filtered = filtered.filter((t) => accountIds.includes(t.account_id));
46
46
  }
47
47
  if (tagNames.length > 0) {
48
48
  if (excludeTags) {
49
49
  filtered = filtered.filter(
50
- (t) => !t.tags?.some((tag) => tagNames.includes(tag.name))
50
+ (t) => !t.transaction_tags?.some((tag) => tagNames.includes(tag.tag_name))
51
51
  );
52
52
  } else {
53
53
  filtered = filtered.filter(
54
- (t) => t.tags?.some((tag) => tagNames.includes(tag.name))
54
+ (t) => t.transaction_tags?.some((tag) => tagNames.includes(tag.tag_name))
55
55
  );
56
56
  }
57
57
  }
58
58
  if (showUntagged) {
59
- filtered = filtered.filter((t) => !t.tags || t.tags.length === 0);
59
+ filtered = filtered.filter((t) => !t.transaction_tags || t.transaction_tags.length === 0);
60
60
  }
61
61
  return filtered;
62
62
  }, [data, type, name, accountIds, tagNames, showUntagged, excludeTags]);
@@ -73,8 +73,8 @@ function useTransactionSummary(options) {
73
73
  }
74
74
  const credits = data.transactions.filter((t) => t.transaction_type === "Credit");
75
75
  const debits = data.transactions.filter((t) => t.transaction_type === "Debit");
76
- const creditAmount = credits.reduce((sum, t) => sum + (t.balance || 0), 0);
77
- const debitAmount = debits.reduce((sum, t) => sum + (t.balance || 0), 0);
76
+ const creditAmount = credits.reduce((sum, t) => sum + (t.amount || 0), 0);
77
+ const debitAmount = debits.reduce((sum, t) => sum + (t.amount || 0), 0);
78
78
  return {
79
79
  totalCount: data.transactions.length,
80
80
  totalAmount: creditAmount - debitAmount,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useTransactionFilters.ts","../src/hooks/useFilteredTransactions.ts","../src/hooks/useTransactionSummary.ts","../src/hooks/useTransactionTagging.ts"],"names":["useMemo","useTransactions"],"mappings":";;;;AAgCO,SAAS,qBAAA,CACd,OAAA,GAAoC,EAAC,EACb;AACxB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,EAAE,MAAM,QAAA,GAAW,IAAI,IAAA,GAAO,EAAC,EAAG,IAAA,EAAK,GAAI,OAAA;AAEjD,IAAA,MAAM,gBAAgB,CAAC,CAAC,QAAQ,IAAA,CAAK,IAAA,GAAO,MAAA,GAAS,CAAA;AACrD,IAAA,MAAM,iBAAA,GAAoB,SAAS,MAAA,GAAS,CAAA;AAC5C,IAAA,MAAM,aAAA,GAAgB,KAAK,MAAA,GAAS,CAAA;AACpC,IAAA,MAAM,gBAAgB,CAAC,CAAC,QAAQ,IAAA,CAAK,IAAA,GAAO,MAAA,GAAS,CAAA;AAErD,IAAA,OAAO;AAAA,MACL,aAAA;AAAA,MACA,iBAAA;AAAA,MACA,aAAA;AAAA,MACA,aAAA;AAAA,MACA,gBAAA,EACE,aAAA,IAAiB,iBAAA,IAAqB,aAAA,IAAiB;AAAA,KAC3D;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,UAAU,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,IAAI,CAAC,CAAA;AACjE;ACtBO,SAAS,wBAAwB,OAAA,EAAwB;AAC9D,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM,UAAA,GAAa,EAAC,EAAG,QAAA,GAAW,EAAC,EAAG,YAAA,EAAc,WAAA,EAAY,GAAI,OAAA;AAE1F,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,eAAA,CAAgB;AAAA,IAC/B,MAAA;AAAA,IACA,OAAA,EAAS;AAAA,MACP,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,QAAQ,OAAA,CAAQ;AAAA;AAClB,GACD,CAAA;AAED,EAAA,OAAOA,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,IAAA,EAAM,YAAA,IAAgB,IAAA,CAAK,YAAA,CAAa,WAAW,CAAA,EAAG;AACzD,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,IAAI,WAAW,IAAA,CAAK,YAAA;AAGpB,IAAA,IAAI,IAAA,IAAQ,IAAA,CAAK,IAAA,EAAK,CAAE,SAAS,CAAA,EAAG;AAClC,MAAA,QAAA,GAAW,SAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,qBAAqB,IAAI,CAAA;AAAA,IAC/D;AAGA,IAAA,IAAI,IAAA,IAAQ,IAAA,CAAK,IAAA,EAAK,CAAE,SAAS,CAAA,EAAG;AAClC,MAAA,MAAM,UAAA,GAAa,KAAK,WAAA,EAAY;AACpC,MAAA,QAAA,GAAW,QAAA,CAAS,MAAA;AAAA,QAAO,CAAC,CAAA,KAC1B,CAAA,CAAE,QAAA,EAAU,aAAY,CAAE,QAAA,CAAS,UAAU,CAAA,IAC7C,CAAA,CAAE,aAAA,EAAe,WAAA,EAAY,CAAE,SAAS,UAAU;AAAA,OACpD;AAAA,IACF;AAGA,IAAA,IAAI,UAAA,CAAW,SAAS,CAAA,EAAG;AACzB,MAAA,QAAA,GAAW,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,UAAA,CAAW,QAAA,CAAS,MAAA,CAAO,CAAA,CAAE,KAAA,EAAO,OAAO,CAAC,CAAC,CAAA;AAAA,IACjF;AAGA,IAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,MAAA,IAAI,WAAA,EAAa;AAEf,QAAA,QAAA,GAAW,QAAA,CAAS,MAAA;AAAA,UAAO,CAAC,CAAA,KAC1B,CAAC,CAAA,CAAE,IAAA,EAAM,IAAA,CAAK,CAAC,GAAA,KAAQ,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,IAAI,CAAC;AAAA,SACpD;AAAA,MACF,CAAA,MAAO;AAEL,QAAA,QAAA,GAAW,QAAA,CAAS,MAAA;AAAA,UAAO,CAAC,CAAA,KAC1B,CAAA,CAAE,IAAA,EAAM,IAAA,CAAK,CAAC,GAAA,KAAQ,QAAA,CAAS,QAAA,CAAS,GAAA,CAAI,IAAI,CAAC;AAAA,SACnD;AAAA,MACF;AAAA,IACF;AAGA,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,QAAA,GAAW,QAAA,CAAS,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,EAAE,IAAA,IAAQ,CAAA,CAAE,IAAA,CAAK,MAAA,KAAW,CAAC,CAAA;AAAA,IAClE;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA,EAAG,CAAC,IAAA,EAAM,IAAA,EAAM,MAAM,UAAA,EAAY,QAAA,EAAU,YAAA,EAAc,WAAW,CAAC,CAAA;AACxE;AC1DO,SAAS,sBAAsB,OAAA,EAAoD;AACxF,EAAA,MAAM,EAAE,MAAA,EAAQ,QAAA,EAAU,MAAA,EAAO,GAAI,OAAA;AAErC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAIC,eAAAA,CAAgB;AAAA,IAC/B,MAAA;AAAA,IACA,OAAA,EAAS,EAAE,QAAA,EAAU,MAAA;AAAO,GAC7B,CAAA;AAED,EAAA,OAAOD,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAM,YAAA,EAAc;AACvB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,OAAA,GAAU,KAAK,YAAA,CAAa,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,qBAAqB,QAAQ,CAAA;AAC/E,IAAA,MAAM,MAAA,GAAS,KAAK,YAAA,CAAa,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,qBAAqB,OAAO,CAAA;AAE7E,IAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,MAAA,CAAO,CAAC,GAAA,EAAK,MAAM,GAAA,IAAO,CAAA,CAAE,OAAA,IAAW,CAAA,CAAA,EAAI,CAAC,CAAA;AACzE,IAAA,MAAM,WAAA,GAAc,MAAA,CAAO,MAAA,CAAO,CAAC,GAAA,EAAK,MAAM,GAAA,IAAO,CAAA,CAAE,OAAA,IAAW,CAAA,CAAA,EAAI,CAAC,CAAA;AAEvE,IAAA,OAAO;AAAA,MACL,UAAA,EAAY,KAAK,YAAA,CAAa,MAAA;AAAA,MAC9B,aAAa,YAAA,GAAe,WAAA;AAAA,MAC5B,aAAa,OAAA,CAAQ,MAAA;AAAA,MACrB,YAAA;AAAA,MACA,YAAY,MAAA,CAAO,MAAA;AAAA,MACnB,WAAA;AAAA,MACA,eAAA,EAAiB,IAAA,CAAK,YAAA,CAAa,MAAA,GAAS;AAAA,KAC9C;AAAA,EACF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AACX;ACZO,SAAS,qBAAA,GAAwB;AAItC,EAAA,MAAM,mBAAA,GAAsBA,OAAAA;AAAA,IAC1B,MAAM,CAAC,OAAA,KAAwC;AAC7C,MAAA,IAAI,OAAA,CAAQ,SAAS,OAAA,EAAS;AAC5B,QAAA,OAAO,CAAA;AAAA,MACT;AACA,MAAA,OAAO,OAAA,CAAQ,MAAM,MAAA,CAAO,CAAC,KAAK,IAAA,KAAS,GAAA,GAAM,IAAA,CAAK,KAAA,EAAO,CAAC,CAAA;AAAA,IAChE,CAAA;AAAA,IACA;AAAC,GACH;AAOA,EAAA,MAAM,eAAA,GAAkBA,OAAAA;AAAA,IACtB,MACE,CAAC,OAAA,EAA6B,iBAAA,KAAkD;AAC9E,MAAA,MAAM,SAAmB,EAAC;AAE1B,MAAA,IAAI,OAAA,CAAQ,SAAS,SAAA,EAAW;AAE9B,QAAA,IAAI,CAAC,OAAA,CAAQ,OAAA,IAAW,OAAA,CAAQ,OAAA,CAAQ,WAAW,CAAA,EAAG;AACpD,UAAA,MAAA,CAAO,KAAK,8BAA8B,CAAA;AAAA,QAC5C;AAGA,QAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,MAAA,CAAO,CAAC,GAAA,KAAQ,CAAC,GAAA,IAAO,GAAA,CAAI,IAAA,EAAK,CAAE,MAAA,KAAW,CAAC,CAAA;AACjF,QAAA,IAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AACxB,UAAA,MAAA,CAAO,KAAK,sBAAsB,CAAA;AAAA,QACpC;AAEA,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,OAAO,MAAA,KAAW,CAAA;AAAA,UAC3B;AAAA,SACF;AAAA,MACF,CAAA,MAAO;AAEL,QAAA,IAAI,CAAC,OAAA,CAAQ,KAAA,IAAS,OAAA,CAAQ,KAAA,CAAM,WAAW,CAAA,EAAG;AAChD,UAAA,MAAA,CAAO,KAAK,qCAAqC,CAAA;AAAA,QACnD;AAGA,QAAA,MAAM,YAAA,GAAe,QAAQ,KAAA,CAAM,MAAA;AAAA,UACjC,CAAC,IAAA,KAAS,CAAC,IAAA,CAAK,IAAA,IAAQ,IAAA,CAAK,IAAA,CAAK,IAAA,EAAK,CAAE,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,KAAA,IAAS;AAAA,SACzE;AACA,QAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,UAAA,MAAA,CAAO,KAAK,kDAAkD,CAAA;AAAA,QAChE;AAEA,QAAA,MAAM,gBAAA,GAAmB,oBAAoB,OAAO,CAAA;AAGpD,QAAA,IAAI,eAAA,GAAuC,MAAA;AAC3C,QAAA,IAAI,sBAAsB,MAAA,EAAW;AAEnC,UAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,gBAAA,GAAmB,iBAAiB,CAAA;AAChE,UAAA,eAAA,GAAkB,UAAA,GAAa,IAAA;AAE/B,UAAA,IAAI,CAAC,eAAA,EAAiB;AACpB,YAAA,MAAA,CAAO,IAAA;AAAA,cACL,CAAA,gBAAA,EAAmB,iBAAiB,OAAA,CAAQ,CAAC,CAAC,CAAA,mCAAA,EAAsC,iBAAA,CAAkB,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA;AAAA,aAClH;AAAA,UACF;AAAA,QACF;AAEA,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,OAAO,MAAA,KAAW,CAAA;AAAA,UAC3B,MAAA;AAAA,UACA,gBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,IACF,CAAA;AAAA,IACF,CAAC,mBAAmB;AAAA,GACtB;AAMA,EAAA,MAAM,aAAA,GAAgBA,OAAAA;AAAA,IACpB,MACE,CAAC,iBAAA,EAA2B,WAAA,EAAuB,KAAA,KAAuC;AACxF,MAAA,IAAI,WAAA,CAAY,MAAA,KAAW,KAAA,CAAM,MAAA,EAAQ;AACvC,QAAA,MAAM,IAAI,MAAM,oDAAoD,CAAA;AAAA,MACtE;AAEA,MAAA,MAAM,eAAA,GAAkB,YAAY,MAAA,CAAO,CAAC,KAAK,CAAA,KAAM,GAAA,GAAM,GAAG,CAAC,CAAA;AACjE,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,eAAA,GAAkB,GAAG,IAAI,IAAA,EAAM;AAC1C,QAAA,MAAM,IAAI,MAAM,6BAA6B,CAAA;AAAA,MAC/C;AAEA,MAAA,OAAO,WAAA,CAAY,GAAA,CAAI,CAAC,UAAA,EAAY,KAAA,MAAW;AAAA,QAC7C,IAAA,EAAM,MAAM,KAAK,CAAA;AAAA,QACjB,eAAA,EAAkB,oBAAoB,UAAA,GAAc;AAAA,OACtD,CAAE,CAAA;AAAA,IACJ,CAAA;AAAA,IACF;AAAC,GACH;AAKA,EAAA,MAAM,aAAA,GAAgBA,OAAAA;AAAA,IACpB,MAAM,CAAC,IAAA,KAA6B;AAClC,MAAA,OAAO,IAAA,CACJ,GAAA,CAAI,CAAC,GAAA,KAAQ,IAAI,IAAA,EAAK,CAAE,WAAA,EAAa,EACrC,MAAA,CAAO,CAAC,GAAA,KAAQ,GAAA,CAAI,SAAS,CAAC,CAAA;AAAA,IACnC,CAAA;AAAA,IACA;AAAC,GACH;AAEA,EAAA,OAAO;AAAA,IACL,mBAAA;AAAA,IACA,eAAA;AAAA,IACA,aAAA;AAAA,IACA;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import { useMemo } from 'react';\n\nexport interface TransactionFilterState {\n hasNameFilter: boolean;\n hasAccountsFilter: boolean;\n hasTagsFilter: boolean;\n hasTypeFilter: boolean;\n hasSearchFilters: boolean;\n}\n\nexport interface TransactionFilterOptions {\n name?: string;\n accounts?: string[];\n tags?: string[];\n type?: string;\n}\n\n/**\n * Check which transaction filters are active\n *\n * Replaces: Filters.hasNameFilter, hasAccountsFilter, hasTagsFilter, hasTypeFilter, hasSearchFilters\n *\n * Business logic:\n * - hasNameFilter: name is not empty\n * - hasAccountsFilter: accounts array has items\n * - hasTagsFilter: tags array has items\n * - hasTypeFilter: type is not empty\n * - hasSearchFilters: any of the above filters are active\n *\n * @param options - Filter options (name, accounts, tags, type)\n * @returns Object with boolean flags for each filter type\n */\nexport function useTransactionFilters(\n options: TransactionFilterOptions = {}\n): TransactionFilterState {\n return useMemo(() => {\n const { name, accounts = [], tags = [], type } = options;\n\n const hasNameFilter = !!name && name.trim().length > 0;\n const hasAccountsFilter = accounts.length > 0;\n const hasTagsFilter = tags.length > 0;\n const hasTypeFilter = !!type && type.trim().length > 0;\n\n return {\n hasNameFilter,\n hasAccountsFilter,\n hasTagsFilter,\n hasTypeFilter,\n hasSearchFilters:\n hasNameFilter || hasAccountsFilter || hasTagsFilter || hasTypeFilter,\n };\n }, [options.name, options.accounts, options.tags, options.type]);\n}\n","import { useMemo } from 'react';\nimport { useTransactions } from '@pfm-platform/transactions-data-access';\n\nexport interface FilterOptions {\n userId: string;\n type?: string;\n name?: string;\n accountIds?: string[];\n tagNames?: string[];\n showUntagged?: boolean;\n excludeTags?: boolean;\n begin_on?: string;\n end_on?: string;\n}\n\n/**\n * Filter transactions by various criteria\n *\n * Replaces: LocalSearch.transactionsByType and filter logic\n *\n * Business logic:\n * - Filter by transaction_type if specified\n * - Filter by name (case-insensitive substring match)\n * - Filter by account IDs\n * - Filter by tag names (or untagged)\n * - Support exclude tags mode\n *\n * @param options - Filter criteria\n * @returns Filtered transactions array\n */\nexport function useFilteredTransactions(options: FilterOptions) {\n const { userId, type, name, accountIds = [], tagNames = [], showUntagged, excludeTags } = options;\n\n const { data } = useTransactions({\n userId,\n filters: {\n begin_on: options.begin_on,\n end_on: options.end_on,\n },\n });\n\n return useMemo(() => {\n if (!data?.transactions || data.transactions.length === 0) {\n return [];\n }\n\n let filtered = data.transactions;\n\n // Filter by type\n if (type && type.trim().length > 0) {\n filtered = filtered.filter((t) => t.transaction_type === type);\n }\n\n // Filter by name (case-insensitive substring)\n if (name && name.trim().length > 0) {\n const searchTerm = name.toLowerCase();\n filtered = filtered.filter((t) =>\n t.nickname?.toLowerCase().includes(searchTerm) ||\n t.original_name?.toLowerCase().includes(searchTerm)\n );\n }\n\n // Filter by account IDs\n if (accountIds.length > 0) {\n filtered = filtered.filter((t) => accountIds.includes(String(t.links?.account)));\n }\n\n // Filter by tags\n if (tagNames.length > 0) {\n if (excludeTags) {\n // Exclude transactions with these tags\n filtered = filtered.filter((t) =>\n !t.tags?.some((tag) => tagNames.includes(tag.name))\n );\n } else {\n // Include only transactions with these tags\n filtered = filtered.filter((t) =>\n t.tags?.some((tag) => tagNames.includes(tag.name))\n );\n }\n }\n\n // Filter for untagged\n if (showUntagged) {\n filtered = filtered.filter((t) => !t.tags || t.tags.length === 0);\n }\n\n return filtered;\n }, [data, type, name, accountIds, tagNames, showUntagged, excludeTags]);\n}\n","import { useMemo } from 'react';\nimport { useTransactions } from '@pfm-platform/transactions-data-access';\n\nexport interface TransactionSummary {\n totalCount: number;\n totalAmount: number;\n creditCount: number;\n creditAmount: number;\n debitCount: number;\n debitAmount: number;\n hasTransactions: boolean;\n}\n\nexport interface SummaryOptions {\n userId: string;\n begin_on?: string;\n end_on?: string;\n}\n\n/**\n * Calculate transaction summary statistics\n *\n * Business logic:\n * - Count total transactions\n * - Calculate total amount (credits - debits)\n * - Separate credit/debit counts and amounts\n * - Provide hasTransactions boolean\n *\n * @param options - User ID and date range\n * @returns Summary with counts, amounts, and hasTransactions flag\n */\nexport function useTransactionSummary(options: SummaryOptions): TransactionSummary | null {\n const { userId, begin_on, end_on } = options;\n\n const { data } = useTransactions({\n userId,\n filters: { begin_on, end_on },\n });\n\n return useMemo(() => {\n if (!data?.transactions) {\n return null;\n }\n\n const credits = data.transactions.filter((t) => t.transaction_type === 'Credit');\n const debits = data.transactions.filter((t) => t.transaction_type === 'Debit');\n\n const creditAmount = credits.reduce((sum, t) => sum + (t.balance || 0), 0);\n const debitAmount = debits.reduce((sum, t) => sum + (t.balance || 0), 0);\n\n return {\n totalCount: data.transactions.length,\n totalAmount: creditAmount - debitAmount,\n creditCount: credits.length,\n creditAmount,\n debitCount: debits.length,\n debitAmount,\n hasTransactions: data.transactions.length > 0,\n };\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport type { TransactionTagging } from '@pfm-platform/shared';\n\nexport interface TaggingValidation {\n isValid: boolean;\n errors: string[];\n totalSplitAmount?: number;\n splitSumMatches?: boolean;\n}\n\nexport interface SplitSuggestion {\n name: string;\n suggestedAmount: number;\n}\n\n/**\n * Business logic helpers for transaction tagging\n *\n * Provides:\n * - Validation for split tagging (amounts must sum to transaction total)\n * - Split suggestions based on percentages\n * - Tag normalization (trim, lowercase)\n *\n * @example Regular Tagging\n * ```tsx\n * const { validateTagging } = useTransactionTagging();\n * const tagging: TransactionTagging = {\n * type: 'regular',\n * repeat: true,\n * regular: ['groceries', 'shopping']\n * };\n * const validation = validateTagging(tagging);\n * ```\n *\n * @example Split Tagging\n * ```tsx\n * const { validateTagging, calculateSplitTotal } = useTransactionTagging();\n * const tagging: TransactionTagging = {\n * type: 'split',\n * split: [\n * { name: 'groceries', value: 45.50 },\n * { name: 'household', value: 25.25 }\n * ]\n * };\n * const total = calculateSplitTotal(tagging);\n * const validation = validateTagging(tagging, 70.75); // transaction amount\n * ```\n */\nexport function useTransactionTagging() {\n /**\n * Calculate total amount from split tagging\n */\n const calculateSplitTotal = useMemo(\n () => (tagging: TransactionTagging): number => {\n if (tagging.type !== 'split') {\n return 0;\n }\n return tagging.split.reduce((sum, item) => sum + item.value, 0);\n },\n []\n );\n\n /**\n * Validate tagging data\n * For split mode: checks if amounts sum to transaction total\n * For regular mode: checks if tags array is not empty\n */\n const validateTagging = useMemo(\n () =>\n (tagging: TransactionTagging, transactionAmount?: number): TaggingValidation => {\n const errors: string[] = [];\n\n if (tagging.type === 'regular') {\n // Regular mode validation\n if (!tagging.regular || tagging.regular.length === 0) {\n errors.push('At least one tag is required');\n }\n\n // Check for empty tag names\n const emptyTags = tagging.regular.filter((tag) => !tag || tag.trim().length === 0);\n if (emptyTags.length > 0) {\n errors.push('Tags cannot be empty');\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n };\n } else {\n // Split mode validation\n if (!tagging.split || tagging.split.length === 0) {\n errors.push('At least one split item is required');\n }\n\n // Check for empty names or invalid amounts\n const invalidItems = tagging.split.filter(\n (item) => !item.name || item.name.trim().length === 0 || item.value <= 0\n );\n if (invalidItems.length > 0) {\n errors.push('Split items must have names and positive amounts');\n }\n\n const totalSplitAmount = calculateSplitTotal(tagging);\n\n // If transaction amount is provided, check if split amounts sum to transaction total\n let splitSumMatches: boolean | undefined = undefined;\n if (transactionAmount !== undefined) {\n // Allow small floating point difference (0.01)\n const difference = Math.abs(totalSplitAmount - transactionAmount);\n splitSumMatches = difference < 0.01;\n\n if (!splitSumMatches) {\n errors.push(\n `Split amounts ($${totalSplitAmount.toFixed(2)}) must sum to transaction amount ($${transactionAmount.toFixed(2)})`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n totalSplitAmount,\n splitSumMatches,\n };\n }\n },\n [calculateSplitTotal]\n );\n\n /**\n * Suggest split amounts based on percentages\n * Useful for common splits like 50/50, 60/40, etc.\n */\n const suggestSplits = useMemo(\n () =>\n (transactionAmount: number, percentages: number[], names: string[]): SplitSuggestion[] => {\n if (percentages.length !== names.length) {\n throw new Error('Percentages and names arrays must have same length');\n }\n\n const totalPercentage = percentages.reduce((sum, p) => sum + p, 0);\n if (Math.abs(totalPercentage - 100) > 0.01) {\n throw new Error('Percentages must sum to 100');\n }\n\n return percentages.map((percentage, index) => ({\n name: names[index],\n suggestedAmount: (transactionAmount * percentage) / 100,\n }));\n },\n []\n );\n\n /**\n * Normalize tag names (trim whitespace, lowercase)\n */\n const normalizeTags = useMemo(\n () => (tags: string[]): string[] => {\n return tags\n .map((tag) => tag.trim().toLowerCase())\n .filter((tag) => tag.length > 0);\n },\n []\n );\n\n return {\n calculateSplitTotal,\n validateTagging,\n suggestSplits,\n normalizeTags,\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/useTransactionFilters.ts","../src/hooks/useFilteredTransactions.ts","../src/hooks/useTransactionSummary.ts","../src/hooks/useTransactionTagging.ts"],"names":["useMemo","useTransactions"],"mappings":"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","file":"index.js","sourcesContent":["import { useMemo } from 'react';\n\nexport interface TransactionFilterState {\n hasNameFilter: boolean;\n hasAccountsFilter: boolean;\n hasTagsFilter: boolean;\n hasTypeFilter: boolean;\n hasSearchFilters: boolean;\n}\n\nexport interface TransactionFilterOptions {\n name?: string;\n accounts?: string[];\n tags?: string[];\n type?: string;\n}\n\n/**\n * Check which transaction filters are active\n *\n * Replaces: Filters.hasNameFilter, hasAccountsFilter, hasTagsFilter, hasTypeFilter, hasSearchFilters\n *\n * Business logic:\n * - hasNameFilter: name is not empty\n * - hasAccountsFilter: accounts array has items\n * - hasTagsFilter: tags array has items\n * - hasTypeFilter: type is not empty\n * - hasSearchFilters: any of the above filters are active\n *\n * @param options - Filter options (name, accounts, tags, type)\n * @returns Object with boolean flags for each filter type\n */\nexport function useTransactionFilters(\n options: TransactionFilterOptions = {}\n): TransactionFilterState {\n return useMemo(() => {\n const { name, accounts = [], tags = [], type } = options;\n\n const hasNameFilter = !!name && name.trim().length > 0;\n const hasAccountsFilter = accounts.length > 0;\n const hasTagsFilter = tags.length > 0;\n const hasTypeFilter = !!type && type.trim().length > 0;\n\n return {\n hasNameFilter,\n hasAccountsFilter,\n hasTagsFilter,\n hasTypeFilter,\n hasSearchFilters:\n hasNameFilter || hasAccountsFilter || hasTagsFilter || hasTypeFilter,\n };\n }, [options.name, options.accounts, options.tags, options.type]);\n}\n","import { useMemo } from 'react';\nimport { useTransactions } from '@pfm-platform/transactions-data-access';\n\nexport interface FilterOptions {\n userId: string;\n type?: string;\n name?: string;\n accountIds?: string[];\n tagNames?: string[];\n showUntagged?: boolean;\n excludeTags?: boolean;\n begin_on?: string;\n end_on?: string;\n}\n\n/**\n * Filter transactions by various criteria\n *\n * Replaces: LocalSearch.transactionsByType and filter logic\n *\n * Business logic:\n * - Filter by transaction_type if specified\n * - Filter by name (case-insensitive substring match)\n * - Filter by account IDs\n * - Filter by tag names (or untagged)\n * - Support exclude tags mode\n *\n * @param options - Filter criteria\n * @returns Filtered transactions array\n */\nexport function useFilteredTransactions(options: FilterOptions) {\n const { userId, type, name, accountIds = [], tagNames = [], showUntagged, excludeTags } = options;\n\n const { data } = useTransactions({\n userId,\n filters: {\n begin_on: options.begin_on,\n end_on: options.end_on,\n },\n });\n\n return useMemo(() => {\n if (!data?.transactions || data.transactions.length === 0) {\n return [];\n }\n\n let filtered = data.transactions;\n\n // Filter by type\n if (type && type.trim().length > 0) {\n filtered = filtered.filter((t) => t.transaction_type === type);\n }\n\n // Filter by name (case-insensitive substring)\n if (name && name.trim().length > 0) {\n const searchTerm = name.toLowerCase();\n filtered = filtered.filter((t) =>\n t.nickname?.toLowerCase().includes(searchTerm) ||\n t.original_name?.toLowerCase().includes(searchTerm)\n );\n }\n\n // Filter by account IDs\n if (accountIds.length > 0) {\n filtered = filtered.filter((t) => accountIds.includes(t.account_id));\n }\n\n // Filter by tags\n if (tagNames.length > 0) {\n if (excludeTags) {\n // Exclude transactions with these tags\n filtered = filtered.filter((t) =>\n !t.transaction_tags?.some((tag) => tagNames.includes(tag.tag_name))\n );\n } else {\n // Include only transactions with these tags\n filtered = filtered.filter((t) =>\n t.transaction_tags?.some((tag) => tagNames.includes(tag.tag_name))\n );\n }\n }\n\n // Filter for untagged\n if (showUntagged) {\n filtered = filtered.filter((t) => !t.transaction_tags || t.transaction_tags.length === 0);\n }\n\n return filtered;\n }, [data, type, name, accountIds, tagNames, showUntagged, excludeTags]);\n}\n","import { useMemo } from 'react';\nimport { useTransactions } from '@pfm-platform/transactions-data-access';\n\nexport interface TransactionSummary {\n totalCount: number;\n totalAmount: number;\n creditCount: number;\n creditAmount: number;\n debitCount: number;\n debitAmount: number;\n hasTransactions: boolean;\n}\n\nexport interface SummaryOptions {\n userId: string;\n begin_on?: string;\n end_on?: string;\n}\n\n/**\n * Calculate transaction summary statistics\n *\n * Business logic:\n * - Count total transactions\n * - Calculate total amount (credits - debits)\n * - Separate credit/debit counts and amounts\n * - Provide hasTransactions boolean\n *\n * @param options - User ID and date range\n * @returns Summary with counts, amounts, and hasTransactions flag\n */\nexport function useTransactionSummary(options: SummaryOptions): TransactionSummary | null {\n const { userId, begin_on, end_on } = options;\n\n const { data } = useTransactions({\n userId,\n filters: { begin_on, end_on },\n });\n\n return useMemo(() => {\n if (!data?.transactions) {\n return null;\n }\n\n const credits = data.transactions.filter((t) => t.transaction_type === 'Credit');\n const debits = data.transactions.filter((t) => t.transaction_type === 'Debit');\n\n const creditAmount = credits.reduce((sum, t) => sum + (t.amount || 0), 0);\n const debitAmount = debits.reduce((sum, t) => sum + (t.amount || 0), 0);\n\n return {\n totalCount: data.transactions.length,\n totalAmount: creditAmount - debitAmount,\n creditCount: credits.length,\n creditAmount,\n debitCount: debits.length,\n debitAmount,\n hasTransactions: data.transactions.length > 0,\n };\n }, [data]);\n}\n","import { useMemo } from 'react';\nimport type { TransactionTagging } from '@pfm-platform/shared';\n\nexport interface TaggingValidation {\n isValid: boolean;\n errors: string[];\n totalSplitAmount?: number;\n splitSumMatches?: boolean;\n}\n\nexport interface SplitSuggestion {\n name: string;\n suggestedAmount: number;\n}\n\n/**\n * Business logic helpers for transaction tagging\n *\n * Provides:\n * - Validation for split tagging (amounts must sum to transaction total)\n * - Split suggestions based on percentages\n * - Tag normalization (trim, lowercase)\n *\n * @example Regular Tagging\n * ```tsx\n * const { validateTagging } = useTransactionTagging();\n * const tagging: TransactionTagging = {\n * type: 'regular',\n * repeat: true,\n * regular: ['groceries', 'shopping']\n * };\n * const validation = validateTagging(tagging);\n * ```\n *\n * @example Split Tagging\n * ```tsx\n * const { validateTagging, calculateSplitTotal } = useTransactionTagging();\n * const tagging: TransactionTagging = {\n * type: 'split',\n * split: [\n * { name: 'groceries', value: 45.50 },\n * { name: 'household', value: 25.25 }\n * ]\n * };\n * const total = calculateSplitTotal(tagging);\n * const validation = validateTagging(tagging, 70.75); // transaction amount\n * ```\n */\nexport function useTransactionTagging() {\n /**\n * Calculate total amount from split tagging\n */\n const calculateSplitTotal = useMemo(\n () => (tagging: TransactionTagging): number => {\n if (tagging.type !== 'split') {\n return 0;\n }\n return tagging.split.reduce((sum, item) => sum + item.value, 0);\n },\n []\n );\n\n /**\n * Validate tagging data\n * For split mode: checks if amounts sum to transaction total\n * For regular mode: checks if tags array is not empty\n */\n const validateTagging = useMemo(\n () =>\n (tagging: TransactionTagging, transactionAmount?: number): TaggingValidation => {\n const errors: string[] = [];\n\n if (tagging.type === 'regular') {\n // Regular mode validation\n if (!tagging.regular || tagging.regular.length === 0) {\n errors.push('At least one tag is required');\n }\n\n // Check for empty tag names\n const emptyTags = tagging.regular.filter((tag) => !tag || tag.trim().length === 0);\n if (emptyTags.length > 0) {\n errors.push('Tags cannot be empty');\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n };\n } else {\n // Split mode validation\n if (!tagging.split || tagging.split.length === 0) {\n errors.push('At least one split item is required');\n }\n\n // Check for empty names or invalid amounts\n const invalidItems = tagging.split.filter(\n (item) => !item.name || item.name.trim().length === 0 || item.value <= 0\n );\n if (invalidItems.length > 0) {\n errors.push('Split items must have names and positive amounts');\n }\n\n const totalSplitAmount = calculateSplitTotal(tagging);\n\n // If transaction amount is provided, check if split amounts sum to transaction total\n let splitSumMatches: boolean | undefined = undefined;\n if (transactionAmount !== undefined) {\n // Allow small floating point difference (0.01)\n const difference = Math.abs(totalSplitAmount - transactionAmount);\n splitSumMatches = difference < 0.01;\n\n if (!splitSumMatches) {\n errors.push(\n `Split amounts ($${totalSplitAmount.toFixed(2)}) must sum to transaction amount ($${transactionAmount.toFixed(2)})`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n totalSplitAmount,\n splitSumMatches,\n };\n }\n },\n [calculateSplitTotal]\n );\n\n /**\n * Suggest split amounts based on percentages\n * Useful for common splits like 50/50, 60/40, etc.\n */\n const suggestSplits = useMemo(\n () =>\n (transactionAmount: number, percentages: number[], names: string[]): SplitSuggestion[] => {\n if (percentages.length !== names.length) {\n throw new Error('Percentages and names arrays must have same length');\n }\n\n const totalPercentage = percentages.reduce((sum, p) => sum + p, 0);\n if (Math.abs(totalPercentage - 100) > 0.01) {\n throw new Error('Percentages must sum to 100');\n }\n\n return percentages.map((percentage, index) => ({\n name: names[index],\n suggestedAmount: (transactionAmount * percentage) / 100,\n }));\n },\n []\n );\n\n /**\n * Normalize tag names (trim whitespace, lowercase)\n */\n const normalizeTags = useMemo(\n () => (tags: string[]): string[] => {\n return tags\n .map((tag) => tag.trim().toLowerCase())\n .filter((tag) => tag.length > 0);\n },\n []\n );\n\n return {\n calculateSplitTotal,\n validateTagging,\n suggestSplits,\n normalizeTags,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@pfm-platform/transactions-feature",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "dependencies": {
7
- "react": "19.2.0",
8
- "@pfm-platform/shared": "0.0.1",
9
- "@pfm-platform/transactions-data-access": "0.1.1"
7
+ "react": "19.2.4",
8
+ "@pfm-platform/shared": "0.2.1",
9
+ "@pfm-platform/transactions-data-access": "0.2.1"
10
10
  },
11
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",
12
+ "@tanstack/react-query": "5.90.21",
13
+ "@testing-library/react": "^16.3.2",
14
+ "@types/react": "^19.2.14",
15
+ "@vitejs/plugin-react": "^5.1.4",
16
+ "@vitest/coverage-v8": "^4.0.18",
17
17
  "jsdom": "^27.2.0",
18
- "react-dom": "19.2.0",
18
+ "react-dom": "19.2.4",
19
19
  "typescript": "5.9.3",
20
- "vitest": "4.0.9"
20
+ "vitest": "4.0.18"
21
21
  },
22
22
  "module": "./dist/index.js",
23
23
  "types": "./dist/index.d.ts",