Compare commits

...

5 Commits

Author SHA1 Message Date
Matiss Janis Aboltins
d0fcfd2d5a Bump version to v24.10.1 2024-10-08 18:05:06 +01:00
Matiss Janis Aboltins
888d2e9cce (synced-prefs) preloading in redux state to improve perf (#3544) 2024-10-07 18:11:02 +01:00
Matiss Janis Aboltins
2e569355bc 🐛 (reports) fix reports page having empty blocks (#3566) 2024-10-07 18:10:51 +01:00
Joel Jeremy Marquez
4a1e1511bc [Mobile] Fix budget list on mobile auto selecting a budget file under the Switch budget file menu (#3574)
* Fix budget list on mobile

* Releast notes
2024-10-07 18:10:36 +01:00
Joel Jeremy Marquez
c456596417 [Mobile] Update to Budgeted to match the column name in mobile budget table (#3573)
* Update to Budgeted to match the column name in mobile budget table

* Release notes

* VRT

* VRT
2024-10-07 18:10:00 +01:00
25 changed files with 187 additions and 120 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/api",
"version": "24.10.0",
"version": "24.10.1",
"license": "MIT",
"description": "An API for Actual",
"engines": {

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/web",
"version": "24.10.0",
"version": "24.10.1",
"license": "MIT",
"files": [
"build"

View File

@@ -78,24 +78,8 @@ function FileMenu({
const { t } = useTranslation();
const items = [{ name: 'delete', text: t('Delete') }];
const { isNarrowWidth } = useResponsive();
const defaultMenuItemStyle = isNarrowWidth
? {
...styles.mobileMenuItem,
color: theme.menuItemText,
borderRadius: 0,
borderTop: `1px solid ${theme.pillBorder}`,
}
: {};
return (
<Menu
getItemStyle={() => defaultMenuItemStyle}
onMenuSelect={onMenuSelect}
items={items}
/>
);
return <Menu onMenuSelect={onMenuSelect} items={items} />;
}
function FileMenuButton({ onDelete }: { onDelete: () => void }) {
@@ -202,50 +186,60 @@ function FileItem({
}
return (
<View
onClick={() => _onSelect(file)}
title={getFileDescription(file, t) || ''}
<Button
onPress={() => _onSelect(file)}
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
...styles.shadow,
margin: 10,
padding: '12px 15px',
backgroundColor: theme.buttonNormalBackground,
borderRadius: 6,
flexShrink: 0,
cursor: 'pointer',
':hover': {
backgroundColor: theme.menuItemBackgroundHover,
},
borderRadius: 6,
borderColor: 'transparent',
}}
>
<View style={{ alignItems: 'flex-start' }}>
<Text style={{ fontSize: 16, fontWeight: 700 }}>{file.name}</Text>
<FileState file={file} />
</View>
<View
style={{ flex: '0 0 auto', flexDirection: 'row', alignItems: 'center' }}
style={{
flexDirection: 'row',
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{file.encryptKeyId && (
<SvgKey
style={{
width: 13,
height: 13,
marginRight: 8,
color: file.hasKey
? theme.formLabelText
: theme.buttonNormalDisabledText,
}}
/>
)}
<View
title={getFileDescription(file, t) || ''}
style={{ alignItems: 'flex-start' }}
>
<Text style={{ fontSize: 16, fontWeight: 700 }}>{file.name}</Text>
{!quickSwitchMode && <FileMenuButton onDelete={() => onDelete(file)} />}
<FileState file={file} />
</View>
<View
style={{
flex: '0 0 auto',
flexDirection: 'row',
alignItems: 'center',
}}
>
{file.encryptKeyId && (
<SvgKey
style={{
width: 13,
height: 13,
marginRight: 8,
color: file.hasKey
? theme.formLabelText
: theme.buttonNormalDisabledText,
}}
/>
)}
{!quickSwitchMode && (
<FileMenuButton onDelete={() => onDelete(file)} />
)}
</View>
</View>
</View>
</Button>
);
}

View File

@@ -81,7 +81,7 @@ export function EnvelopeBudgetMenuModal({
fontWeight: 400,
}}
>
Budget
Budgeted
</Text>
<FocusableAmountInput
value={integerToAmount(budgeted || 0)}

View File

@@ -81,7 +81,7 @@ export function TrackingBudgetMenuModal({
fontWeight: 400,
}}
>
Budget
Budgeted
</Text>
<FocusableAmountInput
value={integerToAmount(budgeted || 0)}

View File

@@ -82,16 +82,26 @@ export function Overview() {
const isDashboardsFeatureEnabled = useFeatureFlag('dashboards');
const spendingReportFeatureFlag = useFeatureFlag('spendingReport');
const baseLayout = widgets.map(widget => ({
i: widget.id,
w: widget.width,
h: widget.height,
minW:
isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 2 : 3,
minH:
isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 1 : 2,
...widget,
}));
const baseLayout = widgets
.map(widget => ({
i: widget.id,
w: widget.width,
h: widget.height,
minW:
isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 2 : 3,
minH:
isCustomReportWidget(widget) || widget.type === 'markdown-card' ? 1 : 2,
...widget,
}))
.filter(item => {
if (isDashboardsFeatureEnabled) {
return true;
}
if (item.type === 'custom-report' && !customReportMap.has(item.meta.id)) {
return false;
}
return true;
});
const layout =
spendingReportFeatureFlag &&

View File

@@ -1,8 +1,8 @@
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useQuery } from 'loot-core/client/query-hooks';
import { send } from 'loot-core/platform/client/fetch';
import { q } from 'loot-core/shared/query';
import { saveSyncedPrefs } from 'loot-core/client/actions';
import { type State } from 'loot-core/client/state-types';
import { type SyncedPrefs } from 'loot-core/src/types/prefs';
type SetSyncedPrefAction<K extends keyof SyncedPrefs> = (
@@ -12,21 +12,14 @@ type SetSyncedPrefAction<K extends keyof SyncedPrefs> = (
export function useSyncedPref<K extends keyof SyncedPrefs>(
prefName: K,
): [SyncedPrefs[K], SetSyncedPrefAction<K>] {
const { data: queryData, overrideData: setQueryData } = useQuery<
[{ value: string | undefined }]
>(
() => q('preferences').filter({ id: prefName }).select('value'),
[prefName],
);
const setLocalPref = useCallback<SetSyncedPrefAction<K>>(
newValue => {
const value = String(newValue);
setQueryData([{ value }]);
send('preferences/save', { id: prefName, value });
const dispatch = useDispatch();
const setPref = useCallback<SetSyncedPrefAction<K>>(
value => {
dispatch(saveSyncedPrefs({ [prefName]: value }));
},
[prefName, setQueryData],
[prefName, dispatch],
);
const pref = useSelector((state: State) => state.prefs.synced[prefName]);
return [queryData?.[0]?.value, setLocalPref];
return [pref, setPref];
}

View File

@@ -1,39 +1,22 @@
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useQuery } from 'loot-core/client/query-hooks';
import { send } from 'loot-core/platform/client/fetch';
import { q } from 'loot-core/shared/query';
import { saveSyncedPrefs } from 'loot-core/client/actions';
import { type State } from 'loot-core/client/state-types';
import { type SyncedPrefs } from 'loot-core/src/types/prefs';
type SetSyncedPrefsAction = (value: Partial<SyncedPrefs>) => void;
/** @deprecated: please use `useSyncedPref` (singular) */
export function useSyncedPrefs(): [SyncedPrefs, SetSyncedPrefsAction] {
const { data: queryData } = useQuery<{ id: string; value: string }[]>(
() => q('preferences').select(['id', 'value']),
[],
const dispatch = useDispatch();
const setPrefs = useCallback<SetSyncedPrefsAction>(
newValue => {
dispatch(saveSyncedPrefs(newValue));
},
[dispatch],
);
const prefs = useMemo<SyncedPrefs>(
() =>
(queryData ?? []).reduce(
(carry, { id, value }) => ({
...carry,
[id]: value,
}),
{},
),
[queryData],
);
const setPrefs = useCallback<SetSyncedPrefsAction>(newValue => {
Object.entries(newValue).forEach(([id, value]) => {
send('preferences/save', {
id: id as keyof SyncedPrefs,
value: String(value),
});
});
}, []);
const prefs = useSelector((state: State) => state.prefs.synced);
return [prefs, setPrefs];
}

View File

@@ -3,7 +3,7 @@
"author": "Actual",
"productName": "Actual",
"description": "A simple and powerful personal finance system",
"version": "24.10.0",
"version": "24.10.1",
"scripts": {
"clean": "rm -rf dist",
"update-client": "bin/update-client",

View File

@@ -1,5 +1,9 @@
import { send } from '../../platform/client/fetch';
import { type GlobalPrefs, type MetadataPrefs } from '../../types/prefs';
import {
type GlobalPrefs,
type MetadataPrefs,
type SyncedPrefs,
} from '../../types/prefs';
import * as constants from '../constants';
import { closeModal } from './modals';
@@ -19,6 +23,7 @@ export function loadPrefs() {
type: constants.SET_PREFS,
prefs,
globalPrefs: await send('load-global-prefs'),
syncedPrefs: await send('preferences/get'),
});
return prefs;
@@ -42,6 +47,7 @@ export function loadGlobalPrefs() {
type: constants.SET_PREFS,
prefs: getState().prefs.local,
globalPrefs,
syncedPrefs: getState().prefs.synced,
});
return globalPrefs;
};
@@ -60,3 +66,20 @@ export function saveGlobalPrefs(
onSaveGlobalPrefs?.();
};
}
export function saveSyncedPrefs(prefs: SyncedPrefs) {
return async (dispatch: Dispatch) => {
await Promise.all(
Object.entries(prefs).map(([prefName, value]) =>
send('preferences/save', {
id: prefName as keyof SyncedPrefs,
value,
}),
),
);
dispatch({
type: constants.MERGE_SYNCED_PREFS,
syncedPrefs: prefs,
});
};
}

View File

@@ -1,4 +1,3 @@
// @ts-strict-ignore
import { send } from '../../platform/client/fetch';
import { getUploadError } from '../../shared/errors';
@@ -32,7 +31,6 @@ export function resetSync() {
}
} else {
await dispatch(sync());
await dispatch(loadPrefs());
}
};
}
@@ -45,8 +43,12 @@ export function sync() {
if ('error' in result) {
return { error: result.error };
}
return {};
// Update the prefs
await dispatch(loadPrefs());
}
return {};
};
}

View File

@@ -10,6 +10,7 @@ export const LOAD_PAYEES = 'LOAD_PAYEES';
export const SET_PREFS = 'SET_PREFS';
export const MERGE_LOCAL_PREFS = 'MERGE_LOCAL_PREFS';
export const MERGE_GLOBAL_PREFS = 'MERGE_GLOBAL_PREFS';
export const MERGE_SYNCED_PREFS = 'MERGE_SYNCED_PREFS';
export const SET_BUDGETS = 'SET_BUDGETS';
export const SET_REMOTE_FILES = 'SET_REMOTE_FILES';
export const SET_ALL_FILES = 'SET_ALL_FILES';

View File

@@ -1,17 +1,22 @@
// @ts-strict-ignore
import * as constants from '../constants';
import type { Action } from '../state-types';
import type { PrefsState } from '../state-types/prefs';
const initialState: PrefsState = {
local: null,
global: null,
local: {},
global: {},
synced: {},
};
export function update(state = initialState, action: Action): PrefsState {
switch (action.type) {
case constants.SET_PREFS:
return { local: action.prefs, global: action.globalPrefs };
return {
...state,
local: action.prefs,
global: action.globalPrefs,
synced: action.syncedPrefs,
};
case constants.MERGE_LOCAL_PREFS:
return {
...state,
@@ -22,6 +27,11 @@ export function update(state = initialState, action: Action): PrefsState {
...state,
global: { ...state.global, ...action.globalPrefs },
};
case constants.MERGE_SYNCED_PREFS:
return {
...state,
synced: { ...state.synced, ...action.syncedPrefs },
};
default:
}

View File

@@ -1,15 +1,21 @@
import type { GlobalPrefs, MetadataPrefs } from '../../types/prefs';
import type {
GlobalPrefs,
MetadataPrefs,
SyncedPrefs,
} from '../../types/prefs';
import type * as constants from '../constants';
export type PrefsState = {
local: MetadataPrefs;
global: GlobalPrefs;
synced: SyncedPrefs;
};
export type SetPrefsAction = {
type: typeof constants.SET_PREFS;
prefs: MetadataPrefs;
globalPrefs: GlobalPrefs;
syncedPrefs: SyncedPrefs;
};
export type MergeLocalPrefsAction = {
@@ -22,7 +28,13 @@ export type MergeGlobalPrefsAction = {
globalPrefs: GlobalPrefs;
};
export type MergeSyncedPrefsAction = {
type: typeof constants.MERGE_SYNCED_PREFS;
syncedPrefs: SyncedPrefs;
};
export type PrefsActions =
| SetPrefsAction
| MergeLocalPrefsAction
| MergeGlobalPrefsAction;
| MergeGlobalPrefsAction
| MergeSyncedPrefsAction;

View File

@@ -18,4 +18,17 @@ const savePreferences = async ({
await db.update('preferences', { id, value });
};
const getPreferences = async (): Promise<SyncedPrefs> => {
const prefs = (await db.all('SELECT id, value FROM preferences')) as Array<{
id: string;
value: string;
}>;
return prefs.reduce<SyncedPrefs>((carry, { value, id }) => {
carry[id as keyof SyncedPrefs] = value;
return carry;
}, {});
};
app.method('preferences/save', mutator(undoable(savePreferences)));
app.method('preferences/get', getPreferences);

View File

@@ -5,4 +5,6 @@ export interface PreferencesHandlers {
id: keyof SyncedPrefs;
value: string | undefined;
}) => Promise<void>;
'preferences/get': () => Promise<SyncedPrefs>;
}

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
SyncedPrefs: preload in redux state and fetch from there; improved performance.

View File

@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [MatissJanis]
---
Reports: fix old reports page having empty blocks.

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [joel-jeremy]
---
[Mobile] Update Budget to Budgeted to match the column name in mobile budget table

View File

@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [joel-jeremy]
---
[Mobile] Fix budget list on mobile auto selecting a budget file under the Switch budget file menu