Skip to content

Commit

Permalink
Allow schedules to skip weekends (#1505)
Browse files Browse the repository at this point in the history
* allow schedules to skip weekends

* wording

* release note

* skip weekend in upcoming dates as well

* Clean UI

* Move switch to the date selection modal
  • Loading branch information
pole95 authored Aug 19, 2023
1 parent 79f4d02 commit 639720b
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ export default function ScheduleDetails({ modalProps, actions, id }) {
start: monthUtils.currentDay(),
frequency: 'monthly',
patterns: [],
skipWeekend: false,
weekendSolveMode: 'after',
};
let schedule = {
posts_transaction: false,
Expand Down Expand Up @@ -411,7 +413,6 @@ export default function ScheduleDetails({ modalProps, actions, id }) {

// This is derived from the date
let repeats = state.fields.date ? !!state.fields.date.frequency : false;

return (
<Modal
title={payee ? `Schedule: ${payee.name}` : 'Schedule'}
Expand Down Expand Up @@ -527,7 +528,7 @@ export default function ScheduleDetails({ modalProps, actions, id }) {
</View>

<Stack direction="row" align="flex-start">
<View style={{ flex: 1 }}>
<View style={{ flex: 1, width: '13.44rem' }}>
{repeats ? (
<RecurringSchedulePicker
value={state.fields.date}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Select from '../common/Select';
import Stack from '../common/Stack';
import Text from '../common/Text';
import View from '../common/View';
import { Checkbox } from '../forms';
import { useTooltip, Tooltip } from '../tooltips';

import DateSelect from './DateSelect';
Expand Down Expand Up @@ -54,6 +55,8 @@ function parseConfig(config) {
interval: 1,
frequency: 'monthly',
patterns: [createMonthlyRecurrence(monthUtils.currentDay())],
skipWeekend: false,
weekendSolveMode: 'before',
}
);
}
Expand Down Expand Up @@ -131,6 +134,22 @@ function reducer(state, action) {
patterns: state.config.patterns.filter(p => p !== action.recurrence),
},
};
case 'set-skip-weekend':
return {
...state,
config: {
...state.config,
skipWeekend: action.skipWeekend,
},
};
case 'set-weekend-solve':
return {
...state,
config: {
...state.config,
weekendSolveMode: action.value,
},
};
default:
return state;
}
Expand Down Expand Up @@ -254,6 +273,9 @@ function RecurringScheduleTooltip({ config: currentConfig, onClose, onSave }) {
config: parseConfig(currentConfig),
});

let skipWeekend = state.config.hasOwnProperty('skipWeekend')
? state.config.skipWeekend
: false;
let dateFormat = useSelector(
state => state.prefs.local.dateFormat || 'MM/dd/yyyy',
);
Expand Down Expand Up @@ -346,6 +368,56 @@ function RecurringScheduleTooltip({ config: currentConfig, onClose, onSave }) {
config.patterns.length > 0 && (
<MonthlyPatterns config={config} dispatch={dispatch} />
)}
<Stack direction="column" style={{ marginTop: 5 }}>
<View
style={{
marginTop: 5,
flex: 1,
flexDirection: 'row',
alignItems: 'center',
userSelect: 'none',
}}
>
<Checkbox
id="form_skipwe"
checked={skipWeekend}
onChange={e => {
dispatch({
type: 'set-skip-weekend',
skipWeekend: e.target.checked,
});
}}
/>
<label
htmlFor="form_skipwe"
style={{ userSelect: 'none', marginRight: 5 }}
>
Move schedule{' '}
</label>
<Select
id="solve_dropdown"
options={[
['before', 'before'],
['after', 'after'],
]}
value={state.config.weekendSolveMode}
onChange={value =>
dispatch({ type: 'set-weekend-solve', value: value })
}
style={{
minHeight: '1px',
width: '5rem',
}}
/>
<label
htmlFor="solve_dropdown"
style={{ userSelect: 'none', marginLeft: 5 }}
>
{' '}
weekend
</label>
</View>
</Stack>
<SchedulePreview previewDates={previewDates} />
<div
style={{ display: 'flex', marginTop: 15, justifyContent: 'flex-end' }}
Expand Down
8 changes: 7 additions & 1 deletion packages/loot-core/src/server/accounts/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ function parseRecurDate(desc) {

return {
type: 'recur',
schedule: new RSchedule({ rrules: rules }),
schedule: new RSchedule({
rrules: rules,
data: {
skipWeekend: desc.skipWeekend,
weekendSolve: desc.weekendSolveMode,
},
}),
};
} catch (e) {
throw new RuleError('parse-recur-date', e.message);
Expand Down
26 changes: 25 additions & 1 deletion packages/loot-core/src/server/schedules/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ export function getNextDate(dateCond, start = new Date()) {

if (dates.length > 0) {
let date = dates[0].date;
if (value.schedule.data.skipWeekend) {
date = getDateWithSkippedWeekend(
date,
value.schedule.data.weekendSolve,
);
}
return dayFromDate(date);
}
}
Expand Down Expand Up @@ -372,7 +378,12 @@ async function getUpcomingDates({ config, count }) {
return schedule
.occurrences({ start: d.startOfDay(new Date()), take: count })
.toArray()
.map(date => dayFromDate(date.date));
.map(date =>
config.skipWeekend
? getDateWithSkippedWeekend(date.date, config.weekendSolveMode)
: date.date,
)
.map(date => dayFromDate(date));
} catch (err) {
captureBreadcrumb(config);
throw err;
Expand Down Expand Up @@ -564,4 +575,17 @@ app.events.on('sync', ({ type, subtype }) => {
}
});

function getDateWithSkippedWeekend(date, solveMode) {
if (d.isWeekend(date)) {
if (solveMode === 'after') {
return d.nextMonday(date);
} else if (solveMode === 'before') {
return d.previousFriday(date);
} else {
throw new Error('Unknown weekend solve mode, this should not happen!');
}
}
return date;
}

export default app;
6 changes: 6 additions & 0 deletions upcoming-release-notes/1505.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [pole95]
---

Allow schedules to skip weekends, and automatically reschedule to before or after the weekend.

0 comments on commit 639720b

Please sign in to comment.