Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to set how far out the upcoming scheduled transactions are shown in the account view. #3310

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d56622d
initial poking around
SamBobBarnes Aug 21, 2024
4b78518
ui changes and setting
SamBobBarnes Aug 22, 2024
27c7889
wip
SamBobBarnes Aug 22, 2024
2b34e3b
null check
SamBobBarnes Aug 23, 2024
2486cdd
removed more than a month
SamBobBarnes Aug 23, 2024
f7ff09d
prettier
SamBobBarnes Aug 23, 2024
6300404
todo
SamBobBarnes Aug 23, 2024
558422a
get status tests
SamBobBarnes Aug 24, 2024
4460f7d
added prefs to app.ts
SamBobBarnes Aug 24, 2024
2246966
updated to use LocalPrefs bc of limitation of app.ts
SamBobBarnes Aug 24, 2024
a3beda0
updated schedules component to use localPrefs
SamBobBarnes Aug 24, 2024
b2720f3
removed console.log
SamBobBarnes Aug 24, 2024
7389f6a
Merge remote-tracking branch 'upstream/master' into Custom-upcoming-s…
SamBobBarnes Aug 24, 2024
44f4553
release notes
SamBobBarnes Aug 24, 2024
88c7670
renamed release noted md
SamBobBarnes Aug 24, 2024
bbc4d9b
eslint fixes
SamBobBarnes Aug 24, 2024
bf966cb
import fix
SamBobBarnes Aug 24, 2024
63fccce
update useEffect dependencies
SamBobBarnes Aug 24, 2024
20de574
Merge branch 'master' into Custom-upcoming-scheduled-transaction-window
SamBobBarnes Aug 25, 2024
6d2642e
removed a comment
SamBobBarnes Aug 30, 2024
19ed191
moved upcoming length setting to schedule page
SamBobBarnes Aug 30, 2024
af65d35
Merge remote-tracking branch 'origin/Custom-upcoming-scheduled-transa…
SamBobBarnes Aug 30, 2024
270ba54
Merge branch 'master' into Custom-upcoming-scheduled-transaction-window
SamBobBarnes Aug 30, 2024
da8c64b
removed import
SamBobBarnes Aug 31, 2024
84ff26b
Merge remote-tracking branch 'origin/Custom-upcoming-scheduled-transa…
SamBobBarnes Aug 31, 2024
7be3b61
Merge branch 'master' into Custom-upcoming-scheduled-transaction-window
SamBobBarnes Sep 5, 2024
8a40f76
made setting collapsible and moved to the top
SamBobBarnes Oct 2, 2024
8586d06
Merge remote-tracking branch 'origin/Custom-upcoming-scheduled-transa…
SamBobBarnes Oct 2, 2024
ba8bac6
Merge remote-tracking branch 'upstream/master' into Custom-upcoming-s…
SamBobBarnes Oct 2, 2024
4828bbb
fixed breaking change
SamBobBarnes Oct 2, 2024
fb51a44
updated to work with updated synced prefs methodology
SamBobBarnes Oct 2, 2024
b863d17
pulled preference from db
SamBobBarnes Oct 2, 2024
1f092de
updated tests with mock reset
SamBobBarnes Oct 2, 2024
8cea110
Merge branch 'master' into Custom-upcoming-scheduled-transaction-window
SamBobBarnes Oct 2, 2024
28e543b
Merge branch 'master' into Custom-upcoming-scheduled-transaction-window
youngcw Oct 7, 2024
b233b1a
update vrt
youngcw Oct 8, 2024
9b725f9
Merge branch 'master' into Custom-upcoming-scheduled-transaction-window
SamBobBarnes Oct 8, 2024
6d4ff08
per coderabbit: upcoming length null value prevention
SamBobBarnes Oct 8, 2024
cf07462
update test to use number inputs rather than string
SamBobBarnes Oct 8, 2024
2768fc4
aria label added to close button
SamBobBarnes Oct 8, 2024
be0e27d
moved before/after each blocks per coderabbitai
SamBobBarnes Oct 8, 2024
e54780d
fixed default value typo
SamBobBarnes Oct 8, 2024
5f72820
updated edit button to use slimmer style
SamBobBarnes Oct 8, 2024
f777969
vrt
youngcw Oct 9, 2024
0d62c38
merge
youngcw Oct 11, 2024
237c1b3
vrt
youngcw Oct 11, 2024
9944187
Merge branch 'master' into Custom-upcoming-scheduled-transaction-window
youngcw Oct 11, 2024
8c96f73
vrt
youngcw Oct 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion packages/desktop-client/src/components/schedules/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import { Button } from '../common/Button2';
import { Search } from '../common/Search';
import { View } from '../common/View';
import { Page } from '../Page';
import { UpcomingLengthSettings } from '../settings/Upcoming';

import { SchedulesTable, type ScheduleItemAction } from './SchedulesTable';
import { type ScheduleItemAction, SchedulesTable } from './SchedulesTable';

export function Schedules() {
const { t } = useTranslation();
Expand Down Expand Up @@ -75,6 +76,15 @@ export function Schedules() {
padding: '0 0 15px',
}}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
padding: '15px 0 0',
}}
>
<UpcomingLengthSettings />
</View>
<View
style={{
flex: 1,
Expand Down
85 changes: 85 additions & 0 deletions packages/desktop-client/src/components/settings/Upcoming.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState } from 'react';
import { Trans } from 'react-i18next';
import { useLocation } from 'react-router-dom';

import { Column } from 'glamor/jsxstyle';

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

import { useSyncedPref } from '../../hooks/useSyncedPref';
import { type CSSProperties, theme } from '../../style';
import { Button } from '../common/Button2';
import { Select } from '../common/Select';
import { Text } from '../common/Text';
import { View } from '../common/View';

import { Setting } from './UI';

const options: {
value: SyncedPrefs['upcomingScheduledTransactionLength'];
label: string;
}[] = [
{ value: '1', label: '1 Day' },
{ value: '7', label: '1 Week' },
{ value: '14', label: '2 Weeks' },
{ value: '30', label: '1 Month' },
];

export function UpcomingLengthSettings() {
const [_upcomingLength, setUpcomingLength] = useSyncedPref(
'upcomingScheduledTransactionLength',
);
const upcomingLength = _upcomingLength || '7';

const selectButtonStyle: CSSProperties = {
':hover': {
backgroundColor: theme.buttonNormalBackgroundHover,
},
};

const location = useLocation();
const [expanded, setExpanded] = useState(location.hash === '#upcomingLength');

return expanded ? (
<Setting
primaryAction={
<View style={{ flexDirection: 'row', gap: '1em' }}>
<Column title="Upcoming Length">
<Select
options={options.map(x => [x.value || '7', x.label])}
value={upcomingLength}
onChange={newValue => setUpcomingLength(newValue)}
style={selectButtonStyle}
/>
</Column>
</View>
}
>
<View style={{ flexDirection: 'row', gap: 20 }}>
<Text>
<strong>Upcoming Length</strong> does not affect how budget data is
stored, and can be changed at any time.
</Text>
<Button
onPress={() => setExpanded(false)}
aria-label="Close upcoming length settings"
>
Close
</Button>
</View>
</Setting>
) : (
<View>
<Button
aria-label="Edit upcoming length settings"
variant="primary"
onPress={() => setExpanded(true)}
>
<Trans>
Edit Upcoming Length (
{options.find(x => x.value === upcomingLength)?.label ?? '1 Week'})
</Trans>
</Button>
</View>
);
}
2 changes: 1 addition & 1 deletion packages/desktop-client/src/components/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { listen } from 'loot-core/src/platform/client/fetch';
import { useActions } from '../../hooks/useActions';
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { useGlobalPref } from '../../hooks/useGlobalPref';
import { useLatestVersion, useIsOutdated } from '../../hooks/useLatestVersion';
import { useIsOutdated, useLatestVersion } from '../../hooks/useLatestVersion';
import { useMetadataPref } from '../../hooks/useMetadataPref';
import { useResponsive } from '../../ResponsiveProvider';
import { theme } from '../../style';
Expand Down
24 changes: 14 additions & 10 deletions packages/loot-core/src/client/data-hooks/schedules.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
// @ts-strict-ignore
import React, {
createContext,
useEffect,
useState,
useContext,
useEffect,
useMemo,
useState,
} from 'react';

import { useSyncedPref } from '@actual-app/web/src/hooks/useSyncedPref';

import { q, type Query } from '../../shared/query';
import { getStatus, getHasTransactionsQuery } from '../../shared/schedules';
import { getHasTransactionsQuery, getStatus } from '../../shared/schedules';
import { type ScheduleEntity } from '../../types/models';
import { getAccountFilter } from '../queries';
import { liveQuery } from '../query-helpers';

export type ScheduleStatusType = ReturnType<typeof getStatus>;
export type ScheduleStatuses = Map<ScheduleEntity['id'], ScheduleStatusType>;

function loadStatuses(schedules: ScheduleEntity[], onData) {
function loadStatuses(schedules: ScheduleEntity[], onData, prefs) {
return liveQuery(getHasTransactionsQuery(schedules), onData, {
mapper: data => {
const hasTrans = new Set(data.filter(Boolean).map(row => row.schedule));

return new Map(
schedules.map(s => [
s.id,
getStatus(s.next_date, s.completed, hasTrans.has(s.id)),
getStatus(s.next_date, s.completed, hasTrans.has(s.id), prefs),
]),
);
},
Expand All @@ -36,15 +38,15 @@ type UseSchedulesResult = {
schedules: ScheduleEntity[];
statuses: ScheduleStatuses;
} | null;

export function useSchedules({
transform,
}: UseSchedulesArgs = {}): UseSchedulesResult {
const [data, setData] = useState<UseSchedulesResult>(null);

const upcomingLength = useSyncedPref('upcomingScheduledTransactionLength')[0];
useEffect(() => {
const query = q('schedules').select('*');
let statusQuery;

const scheduleQuery = liveQuery(
transform ? transform(query) : query,
async (schedules: ScheduleEntity[]) => {
Expand All @@ -53,8 +55,10 @@ export function useSchedules({
statusQuery.unsubscribe();
}

statusQuery = loadStatuses(schedules, (statuses: ScheduleStatuses) =>
setData({ schedules, statuses }),
statusQuery = loadStatuses(
schedules,
(statuses: ScheduleStatuses) => setData({ schedules, statuses }),
upcomingLength,
);
}
},
Expand All @@ -68,7 +72,7 @@ export function useSchedules({
statusQuery.unsubscribe();
}
};
}, [transform]);
}, [upcomingLength, transform]);

return data;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/loot-core/src/server/prefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import { Message, sendMessages } from './sync';
export const BUDGET_TYPES = ['report', 'rollover'] as const;
export type BudgetType = (typeof BUDGET_TYPES)[number];

export const UPCOMING_SCHEDULED_TRANSACTION_LENGTHS = [
'1',
'7',
'14',
'30',
] as const;
export type UpcomingScheduledTransactionLength =
(typeof UPCOMING_SCHEDULED_TRANSACTION_LENGTHS)[number];
SamBobBarnes marked this conversation as resolved.
Show resolved Hide resolved

let prefs: MetadataPrefs = null;

export async function loadPrefs(id?: string): Promise<MetadataPrefs> {
Expand Down
22 changes: 16 additions & 6 deletions packages/loot-core/src/server/schedules/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ import { v4 as uuidv4 } from 'uuid';

import { captureBreadcrumb } from '../../platform/exceptions';
import * as connection from '../../platform/server/connection';
import { dayFromDate, currentDay, parseDate } from '../../shared/months';
import { currentDay, dayFromDate, parseDate } from '../../shared/months';
import { q } from '../../shared/query';
import {
extractScheduleConds,
recurConfigToRSchedule,
getHasTransactionsQuery,
getStatus,
getScheduledAmount,
getStatus,
recurConfigToRSchedule,
} from '../../shared/schedules';
import { Rule, Condition } from '../accounts/rules';
import { Condition, Rule } from '../accounts/rules';
import { addTransactions } from '../accounts/sync';
import {
insertRule,
updateRule,
getRules,
insertRule,
ruleModel,
updateRule,
} from '../accounts/transaction-rules';
import { createApp } from '../app';
import { runQuery as aqlQuery } from '../aql';
Expand Down Expand Up @@ -360,6 +360,7 @@ async function skipNextDate({ id }) {
},
});
}

function discoverSchedules() {
return findSchedules();
}
Expand Down Expand Up @@ -480,11 +481,20 @@ async function advanceSchedulesService(syncSuccess) {
const failedToPost = [];
let didPost = false;

const { data: upcomingLength } = await aqlQuery(
q('preferences')
.filter({ id: 'upcomingScheduledTransactionLength' })
.select('value'),
);

SamBobBarnes marked this conversation as resolved.
Show resolved Hide resolved
const upcomingLengthValue = upcomingLength[0]?.value ?? '7'; // Default to 7 days if not set

for (const schedule of schedules) {
const status = getStatus(
schedule.next_date,
schedule.completed,
hasTrans.has(schedule.id),
upcomingLengthValue,
);

if (status === 'paid') {
Expand Down
Loading