diff --git a/resources/js/components/transactions/edit-transaction-dialog.tsx b/resources/js/components/transactions/edit-transaction-dialog.tsx index c1142408..95359f8b 100644 --- a/resources/js/components/transactions/edit-transaction-dialog.tsx +++ b/resources/js/components/transactions/edit-transaction-dialog.tsx @@ -23,6 +23,7 @@ import { useEncryptionKey } from '@/contexts/encryption-key-context'; import { decrypt, encrypt, importKey } from '@/lib/crypto'; import { getStoredKey } from '@/lib/key-storage'; import { evaluateRulesForNewTransaction } from '@/lib/rule-engine'; +import { appendNoteIfNotPresent } from '@/lib/utils'; import { automationRuleSyncService } from '@/services/automation-rule-sync'; import { transactionSyncService } from '@/services/transaction-sync'; import { @@ -198,11 +199,10 @@ export function EditTransactionDialog({ result.noteIv, ); - if (finalNotes) { - finalNotes = `${finalNotes}\n${decryptedRuleNote}`; - } else { - finalNotes = decryptedRuleNote; - } + finalNotes = appendNoteIfNotPresent( + finalNotes || undefined, + decryptedRuleNote, + ); } } diff --git a/resources/js/hooks/use-dashboard-data.ts b/resources/js/hooks/use-dashboard-data.ts index 9a7bc8b1..935510fb 100644 --- a/resources/js/hooks/use-dashboard-data.ts +++ b/resources/js/hooks/use-dashboard-data.ts @@ -76,7 +76,12 @@ function formatMonth(yearMonth: string): string { const isCurrentYear = date.getFullYear() === new Date().getFullYear(); - return date.toLocaleDateString('en-US', isCurrentYear ? { month: 'short' } : { year: '2-digit', month: 'short' }); + return date.toLocaleDateString( + 'en-US', + isCurrentYear + ? { month: 'short' } + : { year: '2-digit', month: 'short' }, + ); } export function useDashboardData(): DashboardData { diff --git a/resources/js/hooks/use-re-evaluate-all-transactions.tsx b/resources/js/hooks/use-re-evaluate-all-transactions.tsx index b63ed161..052b26c3 100644 --- a/resources/js/hooks/use-re-evaluate-all-transactions.tsx +++ b/resources/js/hooks/use-re-evaluate-all-transactions.tsx @@ -1,6 +1,7 @@ import { decrypt, encrypt, importKey } from '@/lib/crypto'; import { getStoredKey } from '@/lib/key-storage'; import { evaluateRules } from '@/lib/rule-engine'; +import { appendNoteIfNotPresent } from '@/lib/utils'; import { automationRuleSyncService } from '@/services/automation-rule-sync'; import { transactionSyncService } from '@/services/transaction-sync'; import type { Account, Bank } from '@/types/account'; @@ -77,17 +78,23 @@ export function useReEvaluateAllTransactions() { let finalNotesIv = transaction.notes_iv; if (result.note && result.noteIv) { - if (transaction.decryptedNotes) { - const combinedNote = `${transaction.decryptedNotes}\n${await decrypt(result.note, key, result.noteIv)}`; + const decryptedRuleNote = await decrypt( + result.note, + key, + result.noteIv, + ); + const combinedNote = appendNoteIfNotPresent( + transaction.decryptedNotes, + decryptedRuleNote, + ); + + if (combinedNote !== transaction.decryptedNotes) { const encrypted = await encrypt( combinedNote, key, ); finalNotes = encrypted.encrypted; finalNotesIv = encrypted.iv; - } else { - finalNotes = result.note; - finalNotesIv = result.noteIv; } } diff --git a/resources/js/lib/utils.ts b/resources/js/lib/utils.ts index 61fbf3a3..c8b9c57b 100644 --- a/resources/js/lib/utils.ts +++ b/resources/js/lib/utils.ts @@ -16,3 +16,18 @@ export function isSameUrl( export function resolveUrl(url: NonNullable): string { return typeof url === 'string' ? url : url.url; } + +export function appendNoteIfNotPresent( + existingNotes: string | undefined, + newNote: string, +): string { + if (!existingNotes) { + return newNote; + } + + if (existingNotes.includes(newNote)) { + return existingNotes; + } + + return `${existingNotes}\n${newNote}`; +} diff --git a/resources/js/pages/transactions/index.tsx b/resources/js/pages/transactions/index.tsx index d29ed7ac..f065e4da 100644 --- a/resources/js/pages/transactions/index.tsx +++ b/resources/js/pages/transactions/index.tsx @@ -55,6 +55,7 @@ import { consoleDebug } from '@/lib/debug'; import { db } from '@/lib/dexie-db'; import { getStoredKey } from '@/lib/key-storage'; import { evaluateRules } from '@/lib/rule-engine'; +import { appendNoteIfNotPresent } from '@/lib/utils'; import { automationRuleSyncService } from '@/services/automation-rule-sync'; import { transactionSyncService } from '@/services/transaction-sync'; import { type BreadcrumbItem } from '@/types'; @@ -600,18 +601,23 @@ export default function Transactions({ categories, accounts, banks }: Props) { if (result.note && result.noteIv) { consoleDebug('Adding note from rule'); - if (transaction.decryptedNotes) { - const combinedNote = `${transaction.decryptedNotes}\n${await decrypt(result.note, key, result.noteIv)}`; + const decryptedRuleNote = await decrypt( + result.note, + key, + result.noteIv, + ); + const combinedNote = appendNoteIfNotPresent( + transaction.decryptedNotes, + decryptedRuleNote, + ); + + if (combinedNote !== transaction.decryptedNotes) { const encrypted = await encrypt(combinedNote, key); finalNotes = encrypted.encrypted; finalNotesIv = encrypted.iv; - consoleDebug( - 'Combined existing notes with rule note', - ); + consoleDebug('Combined notes with rule note'); } else { - finalNotes = result.note; - finalNotesIv = result.noteIv; - consoleDebug('Set rule note as new note'); + consoleDebug('Rule note already present, skipping'); } } @@ -744,18 +750,23 @@ export default function Transactions({ categories, accounts, banks }: Props) { if (result.note && result.noteIv) { consoleDebug('Adding note from rule'); - if (transaction.decryptedNotes) { - const combinedNote = `${transaction.decryptedNotes}\n${await decrypt(result.note, key, result.noteIv)}`; + const decryptedRuleNote = await decrypt( + result.note, + key, + result.noteIv, + ); + const combinedNote = appendNoteIfNotPresent( + transaction.decryptedNotes, + decryptedRuleNote, + ); + + if (combinedNote !== transaction.decryptedNotes) { const encrypted = await encrypt(combinedNote, key); finalNotes = encrypted.encrypted; finalNotesIv = encrypted.iv; - consoleDebug( - 'Combined existing notes with rule note', - ); + consoleDebug('Combined notes with rule note'); } else { - finalNotes = result.note; - finalNotesIv = result.noteIv; - consoleDebug('Set rule note as new note'); + consoleDebug('Rule note already present, skipping'); } }