Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8639a2a71 | ||
|
|
734191424b | ||
|
|
5d4fcfde00 | ||
|
|
54d7e5460a | ||
|
|
43ebe9e0fd | ||
|
|
515bdf5a74 | ||
|
|
018714610a | ||
|
|
00ee165f8e | ||
|
|
68442ae9e6 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actual-app/api",
|
||||
"version": "6.4.0",
|
||||
"version": "6.5.0",
|
||||
"license": "MIT",
|
||||
"description": "An API for Actual",
|
||||
"engines": {
|
||||
|
||||
@@ -32,11 +32,12 @@ Prerequisites:
|
||||
|
||||
#### Running against the local server
|
||||
|
||||
First start the dev server:
|
||||
First start a dev instance:
|
||||
|
||||
```sh
|
||||
HTTPS=true yarn start
|
||||
```
|
||||
Note the network IP address and port the dev instance is listening on.
|
||||
|
||||
Next, navigate to the root of your project folder, run the standartised docker container, and launch the visual regression tests from within it.
|
||||
|
||||
@@ -47,11 +48,11 @@ docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/
|
||||
# If you receive an error such as "docker: invalid reference format", please instead use the following command:
|
||||
docker run --rm --network host -v ${pwd}:/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash
|
||||
|
||||
# Run the VRT tests: important - they MUST be ran against a HTTPS server
|
||||
E2E_START_URL=https://192.168.0.178:3001 yarn vrt
|
||||
# Run the VRT tests: important - they MUST be ran against a HTTPS server. Use the ip and port noted earlier
|
||||
E2E_START_URL=https://ip:port yarn vrt
|
||||
|
||||
# To update snapshots, use the following command:
|
||||
E2E_START_URL=https://192.168.0.178:3001 yarn vrt --update-snapshots
|
||||
E2E_START_URL=https://ip:port yarn vrt --update-snapshots
|
||||
```
|
||||
|
||||
#### Running against a remote server
|
||||
|
||||
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
@@ -49,7 +49,7 @@ test.describe('Mobile', () => {
|
||||
test('opens the accounts page and asserts on balances', async () => {
|
||||
const accountsPage = await navigation.goToAccountsPage();
|
||||
|
||||
const account = await accountsPage.getNthAccount(0);
|
||||
const account = await accountsPage.getNthAccount(1);
|
||||
|
||||
await expect(account.name).toHaveText('Ally Savings');
|
||||
await expect(account.balance).toHaveText('7,653.00');
|
||||
@@ -58,7 +58,7 @@ test.describe('Mobile', () => {
|
||||
|
||||
test('opens individual account page and checks that filtering is working', async () => {
|
||||
const accountsPage = await navigation.goToAccountsPage();
|
||||
const accountPage = await accountsPage.openNthAccount(1);
|
||||
const accountPage = await accountsPage.openNthAccount(0);
|
||||
|
||||
await expect(accountPage.heading).toHaveText('Bank of America');
|
||||
expect(await accountPage.getBalance()).toBeGreaterThan(0);
|
||||
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actual-app/web",
|
||||
"version": "24.1.0",
|
||||
"version": "24.2.0",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"build"
|
||||
|
||||
@@ -83,11 +83,14 @@ export function AccountHeader({
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const searchInput = useRef(null);
|
||||
const splitsExpanded = useSplitsExpanded();
|
||||
const syncServerStatus = useSyncServerStatus();
|
||||
const isUsingServer = syncServerStatus !== 'no-server';
|
||||
const isServerOffline = syncServerStatus === 'offline';
|
||||
|
||||
let canSync = account && account.account_id;
|
||||
let canSync = account && account.account_id && isUsingServer;
|
||||
if (!account) {
|
||||
// All accounts - check for any syncable account
|
||||
canSync = !!accounts.find(account => !!account.account_id);
|
||||
canSync = !!accounts.find(account => !!account.account_id) && isUsingServer;
|
||||
}
|
||||
|
||||
function onToggleSplits() {
|
||||
@@ -210,7 +213,11 @@ export function AccountHeader({
|
||||
style={{ marginTop: 12 }}
|
||||
>
|
||||
{((account && !account.closed) || canSync) && (
|
||||
<Button type="bare" onClick={canSync ? onSync : onImport}>
|
||||
<Button
|
||||
type="bare"
|
||||
onClick={canSync ? onSync : onImport}
|
||||
disabled={canSync && isServerOffline}
|
||||
>
|
||||
{canSync ? (
|
||||
<>
|
||||
<AnimatedRefresh
|
||||
@@ -222,7 +229,7 @@ export function AccountHeader({
|
||||
}
|
||||
style={{ marginRight: 4 }}
|
||||
/>{' '}
|
||||
Sync
|
||||
{isServerOffline ? 'Sync offline' : 'Sync'}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -199,11 +199,11 @@ export function AccountDetails({
|
||||
</View>
|
||||
<PullToRefresh onRefresh={onRefresh}>
|
||||
<TransactionList
|
||||
account={account}
|
||||
transactions={allTransactions}
|
||||
categories={categories}
|
||||
accounts={accounts}
|
||||
payees={payees}
|
||||
showCategory={!account.offbudget}
|
||||
isNew={isNewTransaction}
|
||||
onLoadMore={onLoadMore}
|
||||
onSelect={onSelectTransaction}
|
||||
|
||||
@@ -22,9 +22,6 @@ import { SyncRefresh } from '../SyncRefresh';
|
||||
import { BudgetTable } from './MobileBudgetTable';
|
||||
import { prewarmMonth, switchBudgetType } from './util';
|
||||
|
||||
const CATEGORY_BUDGET_EDIT_ACTION = 'category-budget';
|
||||
const BALANCE_MENU_OPEN_ACTION = 'balance-menu';
|
||||
|
||||
type BudgetInnerProps = {
|
||||
categories: CategoryEntity[];
|
||||
categoryGroups: CategoryGroupEntity[];
|
||||
@@ -76,8 +73,6 @@ function BudgetInner(props: BudgetInnerProps) {
|
||||
const [currentMonth, setCurrentMonth] = useState(currMonth);
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editingBudgetCategoryId, setEditingBudgetCategoryId] = useState(null);
|
||||
const [openBalanceActionMenuId, setOpenBalanceActionMenuId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
@@ -360,29 +355,6 @@ function BudgetInner(props: BudgetInnerProps) {
|
||||
});
|
||||
};
|
||||
|
||||
const onEditCategoryBudget = id => {
|
||||
onEdit(CATEGORY_BUDGET_EDIT_ACTION, id);
|
||||
};
|
||||
|
||||
const onOpenBalanceActionMenu = id => {
|
||||
onEdit(BALANCE_MENU_OPEN_ACTION, id);
|
||||
};
|
||||
|
||||
const onEdit = (action, id) => {
|
||||
// Do not allow editing if another field is currently being edited.
|
||||
// Cancel the currently editing field in that case.
|
||||
const currentlyEditing = editingBudgetCategoryId || openBalanceActionMenuId;
|
||||
|
||||
setEditingBudgetCategoryId(
|
||||
action === CATEGORY_BUDGET_EDIT_ACTION && !currentlyEditing ? id : null,
|
||||
);
|
||||
setOpenBalanceActionMenuId(
|
||||
action === BALANCE_MENU_OPEN_ACTION && !currentlyEditing ? id : null,
|
||||
);
|
||||
|
||||
return { action, editingId: !currentlyEditing ? id : null };
|
||||
};
|
||||
|
||||
const numberFormat = prefs?.numberFormat || 'comma-dot';
|
||||
const hideFraction = prefs?.hideFraction || false;
|
||||
|
||||
@@ -438,10 +410,6 @@ function BudgetInner(props: BudgetInnerProps) {
|
||||
pushModal={pushModal}
|
||||
onEditGroup={onEditGroup}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
openBalanceActionMenuId={openBalanceActionMenuId}
|
||||
onOpenBalanceActionMenu={onOpenBalanceActionMenu}
|
||||
/>
|
||||
)}
|
||||
</SyncRefresh>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo, useEffect, useRef, useState } from 'react';
|
||||
import React, { memo, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import memoizeOne from 'memoize-one';
|
||||
@@ -7,6 +7,10 @@ import { rolloverBudget, reportBudget } from 'loot-core/src/client/queries';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
|
||||
import { useFeatureFlag } from '../../hooks/useFeatureFlag';
|
||||
import {
|
||||
SingleActiveEditFormProvider,
|
||||
useSingleActiveEditForm,
|
||||
} from '../../hooks/useSingleActiveEditForm';
|
||||
import {
|
||||
SvgArrowThinLeft,
|
||||
SvgArrowThinRight,
|
||||
@@ -134,6 +138,7 @@ function BudgetCell({
|
||||
month,
|
||||
onBudgetAction,
|
||||
onEdit,
|
||||
onBlur,
|
||||
isEditing,
|
||||
}) {
|
||||
const sheetValue = useSheetValue(binding);
|
||||
@@ -146,7 +151,7 @@ function BudgetCell({
|
||||
}
|
||||
|
||||
function onAmountClick() {
|
||||
onEdit?.(categoryId);
|
||||
onEdit?.();
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -162,7 +167,7 @@ function BudgetCell({
|
||||
focused={isEditing}
|
||||
textStyle={{ ...styles.smallText, ...textStyle }}
|
||||
onUpdate={updateBudgetAmount}
|
||||
onBlur={() => onEdit?.(null)}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
<View
|
||||
role="button"
|
||||
@@ -244,10 +249,6 @@ const ExpenseCategory = memo(function ExpenseCategory({
|
||||
style,
|
||||
month,
|
||||
onEdit,
|
||||
isEditingBudget,
|
||||
onEditBudget,
|
||||
isBalanceActionMenuOpen,
|
||||
onOpenBalanceActionMenu,
|
||||
onBudgetAction,
|
||||
show3Cols,
|
||||
showBudgetedCol,
|
||||
@@ -255,11 +256,22 @@ const ExpenseCategory = memo(function ExpenseCategory({
|
||||
const opacity = blank ? 0 : 1;
|
||||
const balanceTooltip = useTooltip();
|
||||
|
||||
useEffect(() => {
|
||||
if (isBalanceActionMenuOpen) {
|
||||
const [isEditingBudget, setIsEditingBudget] = useState(false);
|
||||
const { onRequestActiveEdit, onClearActiveEdit } = useSingleActiveEditForm();
|
||||
|
||||
const onEditBudget = () => {
|
||||
onRequestActiveEdit(`${category.id}-budget`, () => {
|
||||
setIsEditingBudget(true);
|
||||
return () => setIsEditingBudget(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onOpenBalanceActionMenu = () => {
|
||||
onRequestActiveEdit(`${category.id}-balance`, () => {
|
||||
balanceTooltip.open();
|
||||
}
|
||||
}, [isBalanceActionMenuOpen, balanceTooltip]);
|
||||
return () => balanceTooltip.close();
|
||||
});
|
||||
};
|
||||
|
||||
const listItemRef = useRef();
|
||||
|
||||
@@ -317,6 +329,7 @@ const ExpenseCategory = memo(function ExpenseCategory({
|
||||
onBudgetAction={onBudgetAction}
|
||||
isEditing={isEditingBudget}
|
||||
onEdit={onEditBudget}
|
||||
onBlur={onClearActiveEdit}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
@@ -349,7 +362,7 @@ const ExpenseCategory = memo(function ExpenseCategory({
|
||||
>
|
||||
<span
|
||||
role="button"
|
||||
onPointerUp={() => onOpenBalanceActionMenu?.(category.id)}
|
||||
onPointerUp={() => onOpenBalanceActionMenu?.()}
|
||||
onPointerDown={e => e.preventDefault()}
|
||||
>
|
||||
<BalanceWithCarryover
|
||||
@@ -371,7 +384,7 @@ const ExpenseCategory = memo(function ExpenseCategory({
|
||||
monthIndex={monthUtils.getMonthIndex(month)}
|
||||
onBudgetAction={_onBudgetAction}
|
||||
onClose={() => {
|
||||
onOpenBalanceActionMenu?.(null);
|
||||
onClearActiveEdit();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
@@ -382,7 +395,7 @@ const ExpenseCategory = memo(function ExpenseCategory({
|
||||
monthIndex={monthUtils.getMonthIndex(month)}
|
||||
onBudgetAction={_onBudgetAction}
|
||||
onClose={() => {
|
||||
onOpenBalanceActionMenu?.(null);
|
||||
onClearActiveEdit();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -795,10 +808,6 @@ const ExpenseGroup = memo(function ExpenseGroup({
|
||||
editMode,
|
||||
onEditGroup,
|
||||
onEditCategory,
|
||||
editingBudgetCategoryId,
|
||||
onEditCategoryBudget,
|
||||
openBalanceActionMenuId,
|
||||
onOpenBalanceActionMenu,
|
||||
// gestures,
|
||||
month,
|
||||
// onReorderCategory,
|
||||
@@ -873,10 +882,6 @@ const ExpenseGroup = memo(function ExpenseGroup({
|
||||
{group.categories
|
||||
.filter(category => !category.hidden || showHiddenCategories)
|
||||
.map((category, index) => {
|
||||
const isEditingCategoryBudget =
|
||||
editingBudgetCategoryId === category.id;
|
||||
const isBalanceActionMenuOpen =
|
||||
openBalanceActionMenuId === category.id;
|
||||
return (
|
||||
<ExpenseCategory
|
||||
key={category.id}
|
||||
@@ -915,10 +920,6 @@ const ExpenseGroup = memo(function ExpenseGroup({
|
||||
showBudgetedCol={showBudgetedCol}
|
||||
editMode={editMode}
|
||||
onEdit={onEditCategory}
|
||||
isEditingBudget={isEditingCategoryBudget}
|
||||
onEditBudget={onEditCategoryBudget}
|
||||
isBalanceActionMenuOpen={isBalanceActionMenuOpen}
|
||||
onOpenBalanceActionMenu={onOpenBalanceActionMenu}
|
||||
// gestures={gestures}
|
||||
month={month}
|
||||
// onReorder={onReorderCategory}
|
||||
@@ -1019,10 +1020,6 @@ function BudgetGroups({
|
||||
categoryGroups,
|
||||
onEditGroup,
|
||||
onEditCategory,
|
||||
editingBudgetCategoryId,
|
||||
onEditCategoryBudget,
|
||||
openBalanceActionMenuId,
|
||||
onOpenBalanceActionMenu,
|
||||
editMode,
|
||||
gestures,
|
||||
month,
|
||||
@@ -1048,71 +1045,67 @@ function BudgetGroups({
|
||||
const { incomeGroup, expenseGroups } = separateGroups(categoryGroups);
|
||||
|
||||
return (
|
||||
<View
|
||||
data-testid="budget-groups"
|
||||
style={{ flex: '1 0 auto', overflowY: 'auto', paddingBottom: 15 }}
|
||||
>
|
||||
{expenseGroups
|
||||
.filter(group => !group.hidden || showHiddenCategories)
|
||||
.map(group => {
|
||||
return (
|
||||
<ExpenseGroup
|
||||
key={group.id}
|
||||
type={type}
|
||||
group={group}
|
||||
showBudgetedCol={showBudgetedCol}
|
||||
gestures={gestures}
|
||||
month={month}
|
||||
editMode={editMode}
|
||||
onEditGroup={onEditGroup}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
openBalanceActionMenuId={openBalanceActionMenuId}
|
||||
onOpenBalanceActionMenu={onOpenBalanceActionMenu}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
onAddCategory={onAddCategory}
|
||||
onReorderCategory={onReorderCategory}
|
||||
onReorderGroup={onReorderGroup}
|
||||
onBudgetAction={onBudgetAction}
|
||||
show3Cols={show3Cols}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
pushModal={pushModal}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<SingleActiveEditFormProvider formName="mobile-budget-table">
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
data-testid="budget-groups"
|
||||
style={{ flex: '1 0 auto', overflowY: 'auto', paddingBottom: 15 }}
|
||||
>
|
||||
<Button onClick={onAddGroup} style={{ fontSize: 12, margin: 10 }}>
|
||||
Add Group
|
||||
</Button>
|
||||
</View>
|
||||
{expenseGroups
|
||||
.filter(group => !group.hidden || showHiddenCategories)
|
||||
.map(group => {
|
||||
return (
|
||||
<ExpenseGroup
|
||||
key={group.id}
|
||||
type={type}
|
||||
group={group}
|
||||
showBudgetedCol={showBudgetedCol}
|
||||
gestures={gestures}
|
||||
month={month}
|
||||
editMode={editMode}
|
||||
onEditGroup={onEditGroup}
|
||||
onEditCategory={onEditCategory}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
onAddCategory={onAddCategory}
|
||||
onReorderCategory={onReorderCategory}
|
||||
onReorderGroup={onReorderGroup}
|
||||
onBudgetAction={onBudgetAction}
|
||||
show3Cols={show3Cols}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
pushModal={pushModal}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{incomeGroup && (
|
||||
<IncomeGroup
|
||||
type={type}
|
||||
group={incomeGroup}
|
||||
month={month}
|
||||
onAddCategory={onAddCategory}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
editMode={editMode}
|
||||
onEditGroup={onEditGroup}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
onBudgetAction={onBudgetAction}
|
||||
pushModal={pushModal}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<Button onClick={onAddGroup} style={{ fontSize: 12, margin: 10 }}>
|
||||
Add Group
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
{incomeGroup && (
|
||||
<IncomeGroup
|
||||
type={type}
|
||||
group={incomeGroup}
|
||||
month={month}
|
||||
onAddCategory={onAddCategory}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
editMode={editMode}
|
||||
onEditGroup={onEditGroup}
|
||||
onEditCategory={onEditCategory}
|
||||
onBudgetAction={onBudgetAction}
|
||||
pushModal={pushModal}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</SingleActiveEditFormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1143,10 +1136,6 @@ export function BudgetTable({
|
||||
pushModal,
|
||||
onEditGroup,
|
||||
onEditCategory,
|
||||
editingBudgetCategoryId,
|
||||
onEditCategoryBudget,
|
||||
openBalanceActionMenuId,
|
||||
onOpenBalanceActionMenu,
|
||||
}) {
|
||||
const { width } = useResponsive();
|
||||
const show3Cols = width >= 360;
|
||||
@@ -1385,10 +1374,6 @@ export function BudgetTable({
|
||||
editMode={editMode}
|
||||
onEditGroup={onEditGroup}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
openBalanceActionMenuId={openBalanceActionMenuId}
|
||||
onOpenBalanceActionMenu={onOpenBalanceActionMenu}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
onAddCategory={onAddCategory}
|
||||
@@ -1423,10 +1408,6 @@ export function BudgetTable({
|
||||
editMode={editMode}
|
||||
onEditGroup={onEditGroup}
|
||||
onEditCategory={onEditCategory}
|
||||
editingBudgetCategoryId={editingBudgetCategoryId}
|
||||
onEditCategoryBudget={onEditCategoryBudget}
|
||||
openBalanceActionMenuId={openBalanceActionMenuId}
|
||||
onOpenBalanceActionMenu={onOpenBalanceActionMenu}
|
||||
onSaveCategory={onSaveCategory}
|
||||
onDeleteCategory={onDeleteCategory}
|
||||
onAddCategory={onAddCategory}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { type CSSProperties, theme } from '../../style';
|
||||
|
||||
import { Text } from './Text';
|
||||
import { Toggle } from './Toggle';
|
||||
import { View } from './View';
|
||||
|
||||
type KeybindingProps = {
|
||||
@@ -33,6 +34,8 @@ type MenuItem = {
|
||||
text: string;
|
||||
key?: string;
|
||||
style?: CSSProperties;
|
||||
toggle?: boolean;
|
||||
tooltip?: string;
|
||||
};
|
||||
|
||||
type MenuProps<T extends MenuItem = MenuItem> = {
|
||||
@@ -164,23 +167,48 @@ export function Menu<T extends MenuItem>({
|
||||
onMouseEnter={() => setHoveredIndex(idx)}
|
||||
onMouseLeave={() => setHoveredIndex(null)}
|
||||
onClick={() =>
|
||||
!item.disabled && onMenuSelect && onMenuSelect(item.name)
|
||||
!item.disabled &&
|
||||
onMenuSelect &&
|
||||
item.toggle === undefined &&
|
||||
onMenuSelect(item.name)
|
||||
}
|
||||
>
|
||||
{/* Force it to line up evenly */}
|
||||
<Text style={{ lineHeight: 0 }}>
|
||||
{item.icon &&
|
||||
createElement(item.icon, {
|
||||
width: item.iconSize || 10,
|
||||
height: item.iconSize || 10,
|
||||
style: {
|
||||
marginRight: 7,
|
||||
width: item.iconSize || 10,
|
||||
},
|
||||
})}
|
||||
</Text>
|
||||
<Text>{item.text}</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
{item.toggle === undefined ? (
|
||||
<>
|
||||
<Text style={{ lineHeight: 0 }}>
|
||||
{item.icon &&
|
||||
createElement(item.icon, {
|
||||
width: item.iconSize || 10,
|
||||
height: item.iconSize || 10,
|
||||
style: {
|
||||
marginRight: 7,
|
||||
width: item.iconSize || 10,
|
||||
},
|
||||
})}
|
||||
</Text>
|
||||
<Text title={item.tooltip}>{item.text}</Text>
|
||||
<View style={{ flex: 1 }} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<label htmlFor={item.name} title={item.tooltip}>
|
||||
{item.text}
|
||||
</label>
|
||||
<View style={{ flex: 1 }} />
|
||||
<Toggle
|
||||
id={item.name}
|
||||
checked={item.toggle}
|
||||
onColor={theme.pageTextPositive}
|
||||
style={{ marginLeft: 5, ...item.style }}
|
||||
onToggle={() =>
|
||||
!item.disabled &&
|
||||
item.toggle !== undefined &&
|
||||
onMenuSelect(item.name)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{item.key && <Keybinding keyName={item.key} />}
|
||||
</View>
|
||||
);
|
||||
|
||||
75
packages/desktop-client/src/components/common/Toggle.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
|
||||
import { css } from 'glamor';
|
||||
|
||||
import { theme, type CSSProperties } from '../../style';
|
||||
|
||||
type ToggleProps = {
|
||||
id: string;
|
||||
checked: boolean;
|
||||
onToggle?: () => void;
|
||||
onColor?: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
export const Toggle = ({
|
||||
id,
|
||||
checked,
|
||||
onToggle,
|
||||
onColor,
|
||||
style,
|
||||
}: ToggleProps) => {
|
||||
return (
|
||||
<div style={{ marginTop: -20, ...style }}>
|
||||
<input
|
||||
id={id}
|
||||
checked={checked}
|
||||
onChange={onToggle}
|
||||
className={`${css({
|
||||
height: 0,
|
||||
width: 0,
|
||||
visibility: 'hidden',
|
||||
})}`}
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
style={{
|
||||
background: checked ? onColor : theme.checkboxToggleBackground,
|
||||
}}
|
||||
className={`${css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
cursor: 'pointer',
|
||||
width: '32px',
|
||||
height: '16px',
|
||||
borderRadius: '100px',
|
||||
position: 'relative',
|
||||
transition: 'background-color .2s',
|
||||
})}`}
|
||||
htmlFor={id}
|
||||
>
|
||||
<span
|
||||
className={`${css(
|
||||
{
|
||||
content: '',
|
||||
position: 'absolute',
|
||||
top: '2px',
|
||||
left: '2px',
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderRadius: '100px',
|
||||
transition: '0.2s',
|
||||
background: '#fff',
|
||||
boxShadow: '0 0 2px 0 rgba(10, 10, 10, 0.29)',
|
||||
},
|
||||
checked && {
|
||||
left: 'calc(100% - 2px)',
|
||||
transform: 'translateX(-100%)',
|
||||
},
|
||||
)}`}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -58,7 +58,10 @@ export const GoCardlessInitialise = ({
|
||||
In order to enable bank-sync via GoCardless (only for EU banks) you
|
||||
will need to create access credentials. This can be done by creating
|
||||
an account with{' '}
|
||||
<ExternalLink to="https://gocardless.com/" linkColor="purple">
|
||||
<ExternalLink
|
||||
to="https://actualbudget.org/docs/advanced/bank-sync/"
|
||||
linkColor="purple"
|
||||
>
|
||||
GoCardless
|
||||
</ExternalLink>
|
||||
.
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
|
||||
import { theme } from '../../style';
|
||||
import { Button } from '../common/Button';
|
||||
import { Menu } from '../common/Menu';
|
||||
import { Select } from '../common/Select';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { Checkbox } from '../forms';
|
||||
import { Tooltip } from '../tooltips';
|
||||
|
||||
import { CategorySelector } from './CategorySelector';
|
||||
import {
|
||||
@@ -39,6 +41,7 @@ export function ReportSidebar({
|
||||
onChangeDates,
|
||||
onChangeViews,
|
||||
}) {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const onSelectRange = cond => {
|
||||
setDateRange(cond);
|
||||
switch (cond) {
|
||||
@@ -242,70 +245,62 @@ export function ReportSidebar({
|
||||
}}
|
||||
>
|
||||
<Text style={{ width: 40, textAlign: 'right', marginRight: 5 }} />
|
||||
|
||||
<Checkbox
|
||||
id="show-empty-columns"
|
||||
checked={customReportItems.showEmpty}
|
||||
value={customReportItems.showEmpty}
|
||||
onChange={() => setShowEmpty(!customReportItems.showEmpty)}
|
||||
/>
|
||||
<label
|
||||
htmlFor="show-empty-columns"
|
||||
title="Show rows that are zero or blank"
|
||||
style={{ fontSize: 12 }}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setMenuOpen(true);
|
||||
}}
|
||||
style={{
|
||||
color: 'currentColor',
|
||||
padding: '5px 10px',
|
||||
}}
|
||||
>
|
||||
Show Empty Rows
|
||||
</label>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
padding: 5,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Text style={{ width: 40, textAlign: 'right', marginRight: 5 }} />
|
||||
|
||||
<Checkbox
|
||||
id="show-hidden-columns"
|
||||
checked={customReportItems.showOffBudgetHidden}
|
||||
value={customReportItems.showOffBudgetHidden}
|
||||
onChange={() =>
|
||||
setShowOffBudgetHidden(!customReportItems.showOffBudgetHidden)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="show-hidden-columns"
|
||||
title="Show off budget accounts and hidden categories"
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
Off Budget Items
|
||||
</label>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
padding: 5,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Text style={{ width: 40, textAlign: 'right', marginRight: 5 }} />
|
||||
|
||||
<Checkbox
|
||||
id="show-uncategorized"
|
||||
checked={customReportItems.showUncategorized}
|
||||
value={customReportItems.showUncategorized}
|
||||
onChange={() =>
|
||||
setShowUncategorized(!customReportItems.showUncategorized)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="show-uncategorized"
|
||||
title="Show uncategorized transactions"
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
Uncategorized
|
||||
</label>
|
||||
Options
|
||||
{menuOpen && (
|
||||
<Tooltip
|
||||
position="bottom-left"
|
||||
style={{ padding: 0 }}
|
||||
onClose={() => {
|
||||
setMenuOpen(false);
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
onMenuSelect={type => {
|
||||
if (type === 'show-hidden-categories') {
|
||||
setShowOffBudgetHidden(
|
||||
!customReportItems.showOffBudgetHidden,
|
||||
);
|
||||
} else if (type === 'show-empty-rows') {
|
||||
setShowEmpty(!customReportItems.showEmpty);
|
||||
} else if (type === 'show-uncategorized') {
|
||||
setShowUncategorized(
|
||||
!customReportItems.showUncategorized,
|
||||
);
|
||||
}
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
name: 'show-empty-rows',
|
||||
text: 'Show Empty Rows',
|
||||
tooltip: 'Show rows that are zero or blank',
|
||||
toggle: customReportItems.showEmpty,
|
||||
},
|
||||
{
|
||||
name: 'show-hidden-categories',
|
||||
text: 'Show Off Budget',
|
||||
tooltip: 'Show off budget accounts and hidden categories',
|
||||
toggle: customReportItems.showOffBudgetHidden,
|
||||
},
|
||||
{
|
||||
name: 'show-uncategorized',
|
||||
text: 'Show Uncategorized',
|
||||
tooltip: 'Show uncategorized transactions',
|
||||
toggle: customReportItems.showUncategorized,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Button>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
|
||||
@@ -7,7 +7,7 @@ export const adjustTextSize = (
|
||||
let source;
|
||||
switch (type) {
|
||||
case 'variable':
|
||||
source = variableLookup.find(({ value }) => values > value).arr;
|
||||
source = variableLookup.find(({ value }) => values >= value).arr;
|
||||
break;
|
||||
case 'donut':
|
||||
source = donutLookup;
|
||||
|
||||
@@ -1131,10 +1131,10 @@ export const TransactionEdit = props => {
|
||||
|
||||
const Transaction = memo(function Transaction({
|
||||
transaction,
|
||||
account,
|
||||
accounts,
|
||||
categories,
|
||||
payees,
|
||||
showCategory,
|
||||
added,
|
||||
onSelect,
|
||||
style,
|
||||
@@ -1169,11 +1169,15 @@ const Transaction = memo(function Transaction({
|
||||
payee,
|
||||
transferAcct,
|
||||
);
|
||||
const prettyCategory = transferAcct
|
||||
? 'Transfer'
|
||||
: isParent
|
||||
? 'Split'
|
||||
: categoryName;
|
||||
const specialCategory = account?.offbudget
|
||||
? 'Off Budget'
|
||||
: transferAcct
|
||||
? 'Transfer'
|
||||
: isParent
|
||||
? 'Split'
|
||||
: null;
|
||||
|
||||
const prettyCategory = specialCategory || categoryName;
|
||||
|
||||
const isPreview = isPreviewId(id);
|
||||
const isReconciled = transaction.reconciled;
|
||||
@@ -1260,22 +1264,21 @@ const Transaction = memo(function Transaction({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showCategory && (
|
||||
<TextOneLine
|
||||
style={{
|
||||
fontSize: 11,
|
||||
marginTop: 1,
|
||||
fontWeight: '400',
|
||||
color: prettyCategory
|
||||
? theme.tableTextSelected
|
||||
: theme.menuItemTextSelected,
|
||||
fontStyle: prettyCategory ? null : 'italic',
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
{prettyCategory || 'Uncategorized'}
|
||||
</TextOneLine>
|
||||
)}
|
||||
<TextOneLine
|
||||
style={{
|
||||
fontSize: 11,
|
||||
marginTop: 1,
|
||||
fontWeight: '400',
|
||||
color: prettyCategory
|
||||
? theme.tableTextSelected
|
||||
: theme.menuItemTextSelected,
|
||||
fontStyle:
|
||||
specialCategory || !prettyCategory ? 'italic' : undefined,
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
{prettyCategory || 'Uncategorized'}
|
||||
</TextOneLine>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
@@ -1296,11 +1299,11 @@ const Transaction = memo(function Transaction({
|
||||
});
|
||||
|
||||
export function TransactionList({
|
||||
account,
|
||||
accounts,
|
||||
categories,
|
||||
payees,
|
||||
transactions,
|
||||
showCategory,
|
||||
isNew,
|
||||
onSelect,
|
||||
scrollProps = {},
|
||||
@@ -1384,10 +1387,10 @@ export function TransactionList({
|
||||
>
|
||||
<Transaction
|
||||
transaction={transaction}
|
||||
account={account}
|
||||
categories={categories}
|
||||
accounts={accounts}
|
||||
payees={payees}
|
||||
showCategory={showCategory}
|
||||
added={isNew(transaction.id)}
|
||||
onSelect={onSelect} // onSelect(transaction)}
|
||||
/>
|
||||
|
||||
@@ -174,6 +174,7 @@ export const checkboxText = tableText;
|
||||
export const checkboxBackgroundSelected = colorPalette.purple300;
|
||||
export const checkboxBorderSelected = colorPalette.purple300;
|
||||
export const checkboxShadowSelected = colorPalette.purple500;
|
||||
export const checkboxToggleBackground = colorPalette.gray700;
|
||||
|
||||
export const pillBackground = colorPalette.navy800;
|
||||
export const pillBackgroundLight = colorPalette.navy900;
|
||||
|
||||
@@ -174,6 +174,7 @@ export const checkboxText = tableBackground;
|
||||
export const checkboxBackgroundSelected = colorPalette.blue500;
|
||||
export const checkboxBorderSelected = colorPalette.blue500;
|
||||
export const checkboxShadowSelected = colorPalette.blue300;
|
||||
export const checkboxToggleBackground = colorPalette.gray400;
|
||||
|
||||
export const pillBackground = colorPalette.navy150;
|
||||
export const pillBackgroundLight = colorPalette.navy100;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"author": "Actual",
|
||||
"productName": "Actual",
|
||||
"description": "A simple and powerful personal finance system",
|
||||
"version": "24.1.0",
|
||||
"version": "24.2.0",
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist",
|
||||
"update-client": "bin/update-client",
|
||||
|
||||
@@ -2,10 +2,4 @@ BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE accounts ADD COLUMN account_sync_source TEXT;
|
||||
|
||||
UPDATE accounts SET
|
||||
account_sync_source = CASE
|
||||
WHEN account_id THEN 'goCardless'
|
||||
ELSE NULL
|
||||
END;
|
||||
|
||||
COMMIT;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
UPDATE accounts
|
||||
SET
|
||||
account_sync_source = 'goCardless'
|
||||
WHERE account_id IS NOT NULL
|
||||
AND account_sync_source IS NULL;
|
||||
|
||||
COMMIT;
|
||||
@@ -589,13 +589,12 @@ export async function createTestBudget(handlers: Handlers) {
|
||||
{ name: 'House Asset', offBudget: true },
|
||||
{ name: 'Roth IRA', offBudget: true },
|
||||
];
|
||||
await runMutator(() =>
|
||||
batchMessages(async () => {
|
||||
for (const account of accounts) {
|
||||
account.id = await handlers['account-create'](account);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await runMutator(async () => {
|
||||
for (const account of accounts) {
|
||||
account.id = await handlers['account-create'](account);
|
||||
}
|
||||
});
|
||||
|
||||
const payees: Array<MockPayeeEntity> = [
|
||||
{ name: 'Starting Balance' },
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Notification } from '../../client/state-types/notifications';
|
||||
import * as monthUtils from '../../shared/months';
|
||||
import * as db from '../db';
|
||||
|
||||
import { setBudget, getSheetValue } from './actions';
|
||||
import { setBudget, getSheetValue, setGoal } from './actions';
|
||||
import { parse } from './cleanup-template.pegjs';
|
||||
|
||||
export function cleanupTemplate({ month }: { month: string }) {
|
||||
@@ -35,11 +35,20 @@ async function processCleanup(month: string): Promise<Notification> {
|
||||
sheetName,
|
||||
`budget-${category.id}`,
|
||||
);
|
||||
const spent = await getSheetValue(
|
||||
sheetName,
|
||||
`sum-amount-${category.id}`,
|
||||
);
|
||||
await setBudget({
|
||||
category: category.id,
|
||||
month,
|
||||
amount: budgeted - balance,
|
||||
});
|
||||
await setGoal({
|
||||
category: category.id,
|
||||
month,
|
||||
goal: -spent,
|
||||
});
|
||||
num_sources += 1;
|
||||
}
|
||||
if (template.filter(t => t.type === 'sink').length > 0) {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [twk3]
|
||||
---
|
||||
|
||||
Bundle loot-core types into the API
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [HansiWursti]
|
||||
---
|
||||
|
||||
Added cleared and uncleared Balances to Account Mobile View
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Fixing TypeScript issues when enabling `strictFunctionTypes` (pt.5).
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
Refactored MobileBudget component to TypeScript
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [twk3]
|
||||
---
|
||||
|
||||
Switch desktop-client to the Vite JS framework.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [shall0pass]
|
||||
---
|
||||
|
||||
Goals: Refactor schedules file into functions and improve the readability of the code.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [davidkus]
|
||||
---
|
||||
|
||||
Adding filter for reconciled transactions.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 2
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 3
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 4
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 5
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 6
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [carkom]
|
||||
---
|
||||
|
||||
Enabling and formatting "viewLabels" button for custom reports page
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Bugfix
|
||||
authors: [jasonmichalski]
|
||||
---
|
||||
|
||||
Fix net worth graph to show more detail in compact card view
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [Jackenmen]
|
||||
---
|
||||
|
||||
Ask for confirmation when editing date of a locked transaction
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [twk3]
|
||||
---
|
||||
|
||||
TypeScript: Add proper types to runHandler
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [kstockk]
|
||||
---
|
||||
|
||||
Add cleared column in csv export
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Fixing TypeScript issues when enabling `strictFunctionTypes` (pt.4).
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Bugfix
|
||||
authors: [jasonmichalski]
|
||||
---
|
||||
|
||||
Fix when pressing Enter adds an extra split transaction when no split remains
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Features
|
||||
authors: [NikxDa]
|
||||
---
|
||||
|
||||
Add "Distribute" button to distribute remaining split amount across empty splits.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [carkom]
|
||||
---
|
||||
|
||||
Reorganize tableGraph files for custom reports.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [ScottFries, blakegearin, carkom]
|
||||
---
|
||||
|
||||
Add ability to import categories from CSV
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [twk3]
|
||||
---
|
||||
|
||||
Add api tests for payees and transactions
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 7
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 8
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 9
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 10
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 11
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [rjwonder]
|
||||
---
|
||||
|
||||
Update sync.ts with additionalInformation as last resort fallback to prevent Payee being empty
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
migration: rename `nordigen_*` secrets to `gocardless_*`
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Electron-app app store (osx) release
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
eslint: no default exports - part 12
|
||||