Skip to content

Commit

Permalink
Custom upcoming length (#4206)
Browse files Browse the repository at this point in the history
* save button

* release notes

* custom component shows

* custom input and saving working

* updated getUpcomingDays to handle custom values

* close modal after save

* test around getUpcomingDays

* updated to use more accurate timespan calculation

* fix for scheduled events only occurring till end of period

* add a day

* fixed input step down
  • Loading branch information
SamBobBarnes authored Jan 23, 2025
1 parent 1f5e5d4 commit f09f4af
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { Input } from '../common/Input';
import { Select } from '../common/Select';

type CustomUpcomingLengthProps = {
onChange: (value: string) => void;
tempValue: string;
};

export function CustomUpcomingLength({
onChange,
tempValue,
}: CustomUpcomingLengthProps) {
const { t } = useTranslation();

const options = [
{ value: 'day', label: t('Days') },
{ value: 'week', label: t('Weeks') },
{ value: 'month', label: t('Months') },
{ value: 'year', label: t('Years') },
];

let timePeriod = [];
if (tempValue === 'custom') {
timePeriod = ['1', 'day'];
} else {
timePeriod = tempValue.split('-');
}

const [numValue, setNumValue] = useState(parseInt(timePeriod[0]));
const [unit, setUnit] = useState(timePeriod[1]);

useEffect(() => {
onChange(`${numValue}-${unit}`);
}, [numValue, onChange, unit]);

return (
<div
style={{ display: 'flex', alignItems: 'center', gap: 5, marginTop: 10 }}
>
<Input
id="length"
style={{ width: 40 }}
type="number"
min={1}
onChange={e => setNumValue(parseInt(e.target.value))}
defaultValue={numValue || 1}
/>
<Select
options={options.map(x => [x.value, x.label])}
value={unit}
onChange={newValue => setUnit(newValue)}
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';

import { type SyncedPrefs } from 'loot-core/types/prefs';

import { useSyncedPref } from '../../hooks/useSyncedPref';
import { Button } from '../common/Button2';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { Paragraph } from '../common/Paragraph';
import { Select } from '../common/Select';
import { View } from '../common/View';

import { CustomUpcomingLength } from './CustomUpcomingLength';

function useUpcomingLengthOptions() {
const { t } = useTranslation();

Expand All @@ -20,22 +23,48 @@ function useUpcomingLengthOptions() {
{ value: '7', label: t('1 week') },
{ value: '14', label: t('2 weeks') },
{ value: 'oneMonth', label: t('1 month') },
{ value: 'currentMonth', label: t('end of the current month') },
{ value: 'currentMonth', label: t('End of the current month') },
{ value: 'custom', label: t('Custom length') },
];

return { upcomingLengthOptions };
}

function nonCustomUpcomingLengthValues(value: string) {
return (
['1', '7', '14', 'oneMonth', 'currentMonth'].findIndex(x => x === value) ===
-1
);
}

export function UpcomingLength() {
const { t } = useTranslation();
const [_upcomingLength, setUpcomingLength] = useSyncedPref(
'upcomingScheduledTransactionLength',
);

const saveUpcomingLength = () => {
setUpcomingLength(tempUpcomingLength);
};

const { upcomingLengthOptions } = useUpcomingLengthOptions();

const upcomingLength = _upcomingLength || '7';

const [tempUpcomingLength, setTempUpcomingLength] = useState(upcomingLength);
const [useCustomLength, setUseCustomLength] = useState(
nonCustomUpcomingLengthValues(tempUpcomingLength),
);
const [saveActive, setSaveActive] = useState(false);

useEffect(() => {
if (tempUpcomingLength !== upcomingLength) {
setSaveActive(true);
} else {
setSaveActive(false);
}
}, [tempUpcomingLength, upcomingLength]);

return (
<Modal
name="schedules-upcoming-length"
Expand Down Expand Up @@ -65,10 +94,43 @@ export function UpcomingLength() {
x.value || '7',
x.label,
])}
value={upcomingLength}
onChange={newValue => setUpcomingLength(newValue)}
value={
nonCustomUpcomingLengthValues(tempUpcomingLength)
? 'custom'
: tempUpcomingLength
}
onChange={newValue => {
setUseCustomLength(newValue === 'custom');
setTempUpcomingLength(newValue);
}}
/>
{useCustomLength && (
<CustomUpcomingLength
onChange={setTempUpcomingLength}
tempValue={tempUpcomingLength}
/>
)}
</View>
<div
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'end',
marginTop: 20,
}}
>
<Button
isDisabled={!saveActive}
onPress={() => {
saveUpcomingLength();
close();
}}
type="submit"
variant="primary"
>
<Trans>Save</Trans>
</Button>
</div>
</>
)}
</Modal>
Expand Down
22 changes: 21 additions & 1 deletion packages/loot-core/src/shared/schedules.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import MockDate from 'mockdate';

import * as monthUtils from './months';
import { getRecurringDescription, getStatus } from './schedules';
import {
getRecurringDescription,
getStatus,
getUpcomingDays,
} from './schedules';

describe('schedules', () => {
const today = new Date(2017, 0, 1); // Global date when testing is set to 2017-01-01 per monthUtils.currentDay()
Expand Down Expand Up @@ -339,4 +343,20 @@ describe('schedules', () => {
).toBe('Every 2 months on the 17th, until 2021-06-01');
});
});

describe('getUpcomingDays', () => {
it.each([
['1', 1],
['7', 7],
['14', 14],
['oneMonth', 32],
['currentMonth', 31],
['2-day', 2],
['5-week', 35],
['3-month', 91],
['4-year', 1462],
])('value of %s returns %i days', (value: string, expected: number) => {
expect(getUpcomingDays(value)).toEqual(expected);
});
});
});
20 changes: 19 additions & 1 deletion packages/loot-core/src/shared/schedules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ export function describeSchedule(schedule, payee) {

export function getUpcomingDays(upcomingLength = '7'): number {
const today = monthUtils.currentDay();
const month = monthUtils.getMonth(today);

switch (upcomingLength) {
case 'currentMonth': {
Expand All @@ -360,7 +361,6 @@ export function getUpcomingDays(upcomingLength = '7'): number {
return end - day + 1;
}
case 'oneMonth': {
const month = monthUtils.getMonth(today);
return (
monthUtils.differenceInCalendarDays(
monthUtils.nextMonth(month),
Expand All @@ -369,6 +369,24 @@ export function getUpcomingDays(upcomingLength = '7'): number {
);
}
default:
if (upcomingLength.includes('-')) {
const [num, unit] = upcomingLength.split('-');
const value = Math.max(1, parseInt(num, 10));
switch (unit) {
case 'day':
return value;
case 'week':
return value * 7;
case 'month':
const future = monthUtils.addMonths(today, value);
return monthUtils.differenceInCalendarDays(future, month) + 1;
case 'year':
const futureYear = monthUtils.addYears(today, value);
return monthUtils.differenceInCalendarDays(futureYear, month) + 1;
default:
return 7;
}
}
return parseInt(upcomingLength, 10);
}
}
Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/4206.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Features
authors: [ SamBobBarnes ]
---

Add option for custom upcoming length

0 comments on commit f09f4af

Please sign in to comment.