Skip to content

Commit

Permalink
feat: add match rules
Browse files Browse the repository at this point in the history
This adds support for basic match rule editing. Comfort features like re-parsing transactions during the import and adding match rules from a running import will be added at a later date.
  • Loading branch information
morremeyer committed Feb 8, 2025
1 parent b4fa1c6 commit 5339b98
Show file tree
Hide file tree
Showing 12 changed files with 438 additions and 9 deletions.
62 changes: 62 additions & 0 deletions cypress/e2e/matchRules.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createAccount, createBudget } from '../support/setup'

describe('Account: Creation', () => {
beforeEach(() => {
// prepare & select a budget
cy.wrap(createBudget({ name: 'Match Rule Test' })).then(budget => {
cy.wrap(budget).as('budget')
cy.visit('/').get('li').contains('Open').click()
})
})

it('can edit match rules', function () {
cy.wrap(
Cypress.Promise.all([
createAccount(
{ name: 'The Grocery Store', external: true },
this.budget
),
createAccount(
{ name: 'My Favorite Restaurant', external: true },
this.budget
),
])
)

cy.contains('Settings').click()
cy.getByTitle('Edit match rules').first().click()
cy.awaitLoading()

cy.getByTitle('Add match rule').first().click()
cy.getInputFor('Match').first().type('Restaurant*')
cy.getAutocompleteFor('Account').first().type('My Favorite')
cy.contains('My Favorite Rest').click()

cy.getByTitle('Add match rule').first().click()
cy.getInputFor('Match').first().type('Groceries*')
cy.getAutocompleteFor('Account').first().type('Grocery')
cy.contains('The Grocery').click()

cy.getByTitle('Save').click()
cy.contains('Changes saved successfully')

// FIXME: Does not click/delete yet
cy.contains('Delete').first().click({ force: true })
cy.contains('Groceries*').should('not.exist')

cy.getByTitle('Save').click()
cy.contains('Changes saved successfully')

cy.reload()
// We need to wait for a second so that the awaitLoading can actually
// see the loading spinner and wait for it to disappear
cy.wait(1000)
cy.awaitLoading()

// Verify that only one match rule exists
cy.getInputFor('Match').should('have.length', 1)

// Verify that correct match rule still exists
cy.getInputFor('Match').first().should('have.value', 'Restaurant*')
})
})
43 changes: 43 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@types/node": "22.13.1",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "8.23.0",
"@typescript-eslint/parser": "8.23.0",
"@vitejs/plugin-react": "4.3.4",
Expand All @@ -36,7 +37,9 @@
"react-dom": "18.3.1",
"react-i18next": "15.3.0",
"react-router-dom": "6.28.2",
"react-sortablejs": "^6.1.4",
"sass": "1.84.0",
"sortablejs": "^1.15.6",
"stream-browserify": "3.0.0",
"tailwindcss": "3.4.17",
"typescript": "5.7.3",
Expand Down
11 changes: 11 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import BudgetForm from './components/BudgetForm'
import OwnAccountsList from './components/OwnAccountsList'
import ExternalAccountsList from './components/ExternalAccountsList'
import AccountForm from './components/AccountForm'
import MatchRuleList from './components/MatchRuleList'
import TransactionsList from './components/TransactionsList'
import TransactionForm from './components/TransactionForm'
import EnvelopesList from './components/EnvelopesList'
Expand Down Expand Up @@ -195,6 +196,16 @@ const App = () => {
/>
}
/>

<Route
path="/settings/match-rules"
element={
<MatchRuleList
budget={budget}
setNotification={setNotification}
/>
}
/>
</>
)}
<Route path="*" element={<Navigate to="/" replace />} />
Expand Down
8 changes: 1 addition & 7 deletions src/components/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'
import { Combobox } from '@headlessui/react'
import { useTranslation } from 'react-i18next'
import { ArchiveBoxIcon } from '@heroicons/react/24/outline'
import { valueOrDefault } from '../lib/prop-helper'

function classNames(...classes: (string | boolean)[]) {
return classes.filter(Boolean).join(' ')
Expand All @@ -24,13 +25,6 @@ type Props<T> = {
inputWrapperClass?: string
}

const valueOrDefault = (customValue: any, defaultValue: any) => {
if (typeof customValue === 'undefined') {
return defaultValue
}
return customValue
}

const Autocomplete = <T extends ArchivableResource>({
groups,
label,
Expand Down
17 changes: 15 additions & 2 deletions src/components/FormField.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'
import { Tooltip } from 'flowbite-react'
import { valueOrDefault } from '../lib/prop-helper'

type Props = {
type: string
Expand All @@ -13,6 +14,8 @@ type Props = {
note?: string
tooltip?: string
className?: string
inputWrapperClass?: string
labelClass?: string
}

const FormField = ({
Expand All @@ -27,14 +30,19 @@ const FormField = ({
note,
tooltip,
className,
inputWrapperClass,
labelClass,
}: Props) => {
if (type === 'number' && typeof options?.step === 'undefined') {
options = { ...options, step: 'any' }
}

return (
<div className={className || ''}>
<label htmlFor={name} className="form-field--label">
<label
htmlFor={name}
className={`${valueOrDefault(labelClass, 'form-field--label')}`}
>
<span className="flex items-center">
{label}
{typeof tooltip !== 'undefined' ? (
Expand All @@ -55,7 +63,12 @@ const FormField = ({
</span>
</label>

<div className={'input--outer sm:col-span-2'}>
<div
className={`${valueOrDefault(
inputWrapperClass,
'input--outer sm:col-span-2'
)}`}
>
<input
className="input"
type={type}
Expand Down
Loading

0 comments on commit 5339b98

Please sign in to comment.