From b10178965d17a14c23658e36f5cb599284bc61b8 Mon Sep 17 00:00:00 2001 From: Morre Date: Sat, 23 Nov 2024 19:19:44 +0100 Subject: [PATCH 1/2] feat!: set default available from to next month With this, the default AvailableFrom date is set to the first of the month following the transaction, not the month of the transaction. This makes more sense logically since it enforces funds to only be allocated after they have been received by default. The value can still be explicitly set if that is required. The onChange function now just keeps the existing value and does not try to set it to the first of the next month, since that would set a future date every time the field is edited. --- cypress/e2e/transaction-import.cy.ts | 2 +- cypress/e2e/transactions.cy.ts | 12 ++++++------ src/components/TransactionForm.tsx | 6 +++--- src/components/TransactionImport/Result.tsx | 6 +++--- src/lib/dates.ts | 12 ++++++++---- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/cypress/e2e/transaction-import.cy.ts b/cypress/e2e/transaction-import.cy.ts index 62de218d..c94a1655 100644 --- a/cypress/e2e/transaction-import.cy.ts +++ b/cypress/e2e/transaction-import.cy.ts @@ -154,7 +154,7 @@ describe('Transaction Import', () => { // Now at the last transaction, import this too cy.contains('5 of 5') - cy.getInputFor('Available From').should('have.value', '2023-06-01') + cy.getInputFor('Available From').should('have.value', '2023-07-01') cy.get('button').contains('Import').click() // Now back at the second transactions since we skipped it diff --git a/cypress/e2e/transactions.cy.ts b/cypress/e2e/transactions.cy.ts index 227e561b..3036665e 100644 --- a/cypress/e2e/transactions.cy.ts +++ b/cypress/e2e/transactions.cy.ts @@ -6,15 +6,13 @@ import { createTransactions, } from '../support/setup' import { Budget, Account, Envelope } from '../../src/types' -import { dateFromIsoString } from '../../src/lib/dates' +import { dateFromIsoString, setToFirstOfNextMonth } from '../../src/lib/dates' describe('Transactions', () => { const date = new Date() // Get the current month in YYYY-MM-01 format - const currentMonth = `${new Date(Date.now()) - .toISOString() - .substring(0, 7)}-01` + const currentMonth = `${date.toISOString().substring(0, 7)}-01` beforeEach(() => { // prepare a budget with two internal & one external accounts @@ -112,8 +110,10 @@ describe('Transactions', () => { cy.getAutocompleteFor('Destination').type('Bank ac') cy.contains('Bank account').click() - cy.getInputFor('Available From').type(currentMonth) - cy.getInputFor('Available From').should('have.value', currentMonth) + cy.getInputFor('Available From').should( + 'have.value', + setToFirstOfNextMonth(date.toISOString().split('T')[0]) + ) cy.getAutocompleteFor('Envelope').type('Onl') cy.contains('Only one').click() diff --git a/src/components/TransactionForm.tsx b/src/components/TransactionForm.tsx index d9a63450..2ae2b831 100644 --- a/src/components/TransactionForm.tsx +++ b/src/components/TransactionForm.tsx @@ -8,7 +8,7 @@ import { dateFromIsoString, dateToIsoString, monthYearFromDate, - setToFirstOfTheMonth, + setToFirstOfNextMonth, } from '../lib/dates' import { safeName } from '../lib/name-helper' import { @@ -413,7 +413,7 @@ const TransactionForm = ({ budget, setNotification }: Props) => { value={(isSupported.inputTypeMonth() ? (date: string) => monthYearFromDate(new Date(date)) : (date: string) => - setToFirstOfTheMonth(dateFromIsoString(date)))( + setToFirstOfNextMonth(dateFromIsoString(date)))( transaction.availableFrom || transaction.date || new Date().toISOString() @@ -423,7 +423,7 @@ const TransactionForm = ({ budget, setNotification }: Props) => { if (e.target.value) { updateValue( 'availableFrom', - dateToIsoString(setToFirstOfTheMonth(e.target.value)) + dateToIsoString(e.target.value) ) } }} diff --git a/src/components/TransactionImport/Result.tsx b/src/components/TransactionImport/Result.tsx index 2f957ffe..8c0b81d6 100644 --- a/src/components/TransactionImport/Result.tsx +++ b/src/components/TransactionImport/Result.tsx @@ -21,7 +21,7 @@ import { dateFromIsoString, dateToIsoString, monthYearFromDate, - setToFirstOfTheMonth, + setToFirstOfNextMonth, } from '../../lib/dates' import { api } from '../../lib/api/base' import { safeName } from '../../lib/name-helper' @@ -436,7 +436,7 @@ const Result = (props: Props) => { value={(isSupported.inputTypeMonth() ? (date: string) => monthYearFromDate(new Date(date)) : (date: string) => - setToFirstOfTheMonth(dateFromIsoString(date)))( + setToFirstOfNextMonth(dateFromIsoString(date)))( currentTransaction().availableFrom || currentTransaction().date || new Date().toISOString() @@ -446,7 +446,7 @@ const Result = (props: Props) => { if (e.target.value) { updateValue( 'availableFrom', - dateToIsoString(setToFirstOfTheMonth(e.target.value)) + dateToIsoString(e.target.value) ) } }} diff --git a/src/lib/dates.ts b/src/lib/dates.ts index 4c5557f4..4e037e48 100644 --- a/src/lib/dates.ts +++ b/src/lib/dates.ts @@ -23,9 +23,13 @@ const dateFromMonthYear = (date: string) => { return new Date(`${month}/15/${year}`) } -const setToFirstOfTheMonth = (date: string) => { - const [year, month] = date.split('-') - return `${year}-${month}-01` +const setToFirstOfNextMonth = (date: string) => { + const d = new Date(date) + d.setDate(1) + d.setMonth(d.getMonth() + 1) + + // Return date part of ISO string + return d.toISOString().split('T')[0] } export { @@ -33,7 +37,7 @@ export { dateToIsoString, monthYearFromDate, dateFromMonthYear, - setToFirstOfTheMonth, + setToFirstOfNextMonth, translatedMonthFormat, shortTranslatedMonthFormat, } From 70600ddee2ba99774ca8463419727537dea08609 Mon Sep 17 00:00:00 2001 From: Morre Date: Sat, 23 Nov 2024 22:27:29 +0100 Subject: [PATCH 2/2] fixup! feat!: set default available from to next month --- cypress/e2e/transactions.cy.ts | 5 ++- src/components/TransactionForm.tsx | 36 +++++++++++++++++---- src/components/TransactionImport/Result.tsx | 3 +- src/lib/dates.ts | 10 ++++-- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/cypress/e2e/transactions.cy.ts b/cypress/e2e/transactions.cy.ts index 3036665e..127a5f91 100644 --- a/cypress/e2e/transactions.cy.ts +++ b/cypress/e2e/transactions.cy.ts @@ -112,9 +112,12 @@ describe('Transactions', () => { cy.getInputFor('Available From').should( 'have.value', - setToFirstOfNextMonth(date.toISOString().split('T')[0]) + setToFirstOfNextMonth(dateFromIsoString(date.toISOString())) ) + cy.getInputFor('Available From').type(currentMonth) + cy.getInputFor('Available From').should('have.value', currentMonth) + cy.getAutocompleteFor('Envelope').type('Onl') cy.contains('Only one').click() diff --git a/src/components/TransactionForm.tsx b/src/components/TransactionForm.tsx index 2ae2b831..af1737e5 100644 --- a/src/components/TransactionForm.tsx +++ b/src/components/TransactionForm.tsx @@ -8,6 +8,7 @@ import { dateFromIsoString, dateToIsoString, monthYearFromDate, + setToFirstOfTheMonth, setToFirstOfNextMonth, } from '../lib/dates' import { safeName } from '../lib/name-helper' @@ -117,6 +118,18 @@ const TransactionForm = ({ budget, setNotification }: Props) => { setTransaction({ ...transaction, [key]: value }) } + const updateValues = ( + values: { key: keyof UnpersistedTransaction; value: any }[] + ) => { + const newTransaction = { ...transaction } + values.forEach( + (value: { key: keyof UnpersistedTransaction; value: any }) => { + newTransaction[value.key] = value.value + } + ) + setTransaction(newTransaction) + } + const createNewResources = async () => { const promises = [] let { sourceAccountId, destinationAccountId } = transaction @@ -394,7 +407,15 @@ const TransactionForm = ({ budget, setNotification }: Props) => { onChange={e => { // value is empty string for invalid dates (e.g. when prefixing month with 0 while typing) – we want to ignore that and keep the previous input if (e.target.value) { - updateValue('date', dateToIsoString(e.target.value)) + updateValues([ + { key: 'date', value: dateToIsoString(e.target.value) }, + { + key: 'availableFrom', + value: dateToIsoString( + setToFirstOfNextMonth(e.target.value) + ), + }, + ]) } }} options={{ disabled: transaction.reconciled || false }} @@ -412,18 +433,19 @@ const TransactionForm = ({ budget, setNotification }: Props) => { }`} value={(isSupported.inputTypeMonth() ? (date: string) => monthYearFromDate(new Date(date)) - : (date: string) => - setToFirstOfNextMonth(dateFromIsoString(date)))( - transaction.availableFrom || - transaction.date || - new Date().toISOString() + : (date: string) => date)( + dateFromIsoString(transaction.availableFrom || '') || + setToFirstOfNextMonth( + dateFromIsoString(transaction.date || '') || + new Date().toISOString() + ) )} onChange={e => { // value is empty string for invalid dates (e.g. when prefixing month with 0 while typing) – we want to ignore that and keep the previous input if (e.target.value) { updateValue( 'availableFrom', - dateToIsoString(e.target.value) + dateToIsoString(setToFirstOfTheMonth(e.target.value)) ) } }} diff --git a/src/components/TransactionImport/Result.tsx b/src/components/TransactionImport/Result.tsx index 8c0b81d6..50e46731 100644 --- a/src/components/TransactionImport/Result.tsx +++ b/src/components/TransactionImport/Result.tsx @@ -21,6 +21,7 @@ import { dateFromIsoString, dateToIsoString, monthYearFromDate, + setToFirstOfTheMonth, setToFirstOfNextMonth, } from '../../lib/dates' import { api } from '../../lib/api/base' @@ -446,7 +447,7 @@ const Result = (props: Props) => { if (e.target.value) { updateValue( 'availableFrom', - dateToIsoString(e.target.value) + dateToIsoString(setToFirstOfTheMonth(e.target.value)) ) } }} diff --git a/src/lib/dates.ts b/src/lib/dates.ts index 4e037e48..19c06a8d 100644 --- a/src/lib/dates.ts +++ b/src/lib/dates.ts @@ -23,13 +23,18 @@ const dateFromMonthYear = (date: string) => { return new Date(`${month}/15/${year}`) } +const setToFirstOfTheMonth = (date: string) => { + const [year, month] = date.split('-') + return `${year}-${month}-01` +} + const setToFirstOfNextMonth = (date: string) => { const d = new Date(date) d.setDate(1) d.setMonth(d.getMonth() + 1) - // Return date part of ISO string - return d.toISOString().split('T')[0] + // Return date in YYYY-MM-DD format + return dateFromIsoString(d.toISOString()) } export { @@ -37,6 +42,7 @@ export { dateToIsoString, monthYearFromDate, dateFromMonthYear, + setToFirstOfTheMonth, setToFirstOfNextMonth, translatedMonthFormat, shortTranslatedMonthFormat,