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

Duplicating Budget and Budget Snapshots #3689

Closed
Closed
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
7127d46
Initial Commit
tlesicka Oct 19, 2024
74004f5
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 19, 2024
62f5175
Fixed database copy error
tlesicka Oct 19, 2024
597b0fa
Removing unused code
tlesicka Oct 19, 2024
f90fc5d
Create 3689.md
tlesicka Oct 19, 2024
b4f2aae
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 20, 2024
e96e646
Fixed issue with database duplication
tlesicka Oct 20, 2024
133a7ed
fixed lint and typecheck errors
tlesicka Oct 20, 2024
d0dcb28
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 21, 2024
eb8f270
Fixed small errors
tlesicka Oct 21, 2024
e423ff9
Ported backups from electron to web-app
tlesicka Oct 22, 2024
3b7ceb8
Fixed lint issues
tlesicka Oct 22, 2024
d56f647
Fixed backups not being deleted
tlesicka Oct 22, 2024
f74a910
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 22, 2024
88293b0
Fixed a few errors reported by coderabbitai
tlesicka Oct 22, 2024
6f1e542
fixed typo
tlesicka Oct 22, 2024
63d2ea6
Fixed issues from coderabbitai
tlesicka Oct 22, 2024
e287c41
Applying coderabbitai recommendations
tlesicka Oct 22, 2024
9816e25
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 22, 2024
bc0881e
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 23, 2024
0932913
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 23, 2024
007c8f0
Fixed issues that required new budget to open
tlesicka Oct 23, 2024
62fb59b
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 23, 2024
83bd491
Added loading spinners to Backup modal
tlesicka Oct 23, 2024
f275142
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Oct 23, 2024
6ce0032
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 23, 2024
3a70c01
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Oct 24, 2024
7e8a4ca
Merge branch 'snapshot-and-duplicate-budget' of https://github.com/tl…
tlesicka Oct 24, 2024
8e81f44
Updated as recommended by coderabbitai
tlesicka Oct 24, 2024
dbf13b6
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Oct 24, 2024
459ed61
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Oct 24, 2024
6d9a2f7
Added onComplete to DuplicateFileModal
tlesicka Oct 25, 2024
c967053
Merge branch 'snapshot-and-duplicate-budget' of https://github.com/tl…
tlesicka Oct 25, 2024
b99d314
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 25, 2024
0b226b8
Fixed issues recommended by coderabbitai
tlesicka Oct 25, 2024
86faf0b
Added user notification to LoadBackupModal and DuplicateFileModal
tlesicka Oct 25, 2024
ef9eb70
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 25, 2024
c717805
Added definition file for backups.{platform}.ts, Added better typing
tlesicka Oct 26, 2024
66760ad
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 26, 2024
49306d3
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Oct 27, 2024
f70a44a
Removed unused/commented code; applied coderabbitai recommendations
tlesicka Oct 27, 2024
4168aac
Fixed typechecking and removed ts-strict-ignore from server/backup.*.ts
tlesicka Oct 27, 2024
d568640
Updated as recommended by coderabbitai
tlesicka Oct 27, 2024
b5ab478
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Oct 27, 2024
9b4d6ab
Fixed issue with backups not showing up in web-app
tlesicka Oct 27, 2024
62274b1
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Oct 28, 2024
d52e8cf
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Oct 29, 2024
22e26a0
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Oct 31, 2024
63b468f
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Nov 4, 2024
65ed930
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Nov 5, 2024
aea26fa
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Nov 6, 2024
ae16f6a
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Nov 6, 2024
e375dbc
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Nov 7, 2024
ff57ec3
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Nov 7, 2024
437ca4e
Updating menu from merge
tlesicka Nov 7, 2024
c5db05b
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Nov 7, 2024
30bc01c
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Nov 12, 2024
5ef098b
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Nov 15, 2024
b023dab
Fixed errors from merging upstream/master
tlesicka Nov 15, 2024
b042059
Merge remote-tracking branch 'upstream/master' into snapshot-and-dupl…
tlesicka Nov 16, 2024
5b1c80a
Removed <Trans> tags
tlesicka Nov 16, 2024
61f4e22
Merge branch 'actualbudget:master' into snapshot-and-duplicate-budget
tlesicka Nov 19, 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
11 changes: 11 additions & 0 deletions packages/desktop-client/src/components/Modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { KeyboardShortcutModal } from './modals/KeyboardShortcutModal';
import { LoadBackupModal } from './modals/LoadBackupModal';
import { ConfirmChangeDocumentDirModal } from './modals/manager/ConfirmChangeDocumentDir';
import { DeleteFileModal } from './modals/manager/DeleteFileModal';
import { DuplicateFileModal } from './modals/manager/DuplicateFileModal';
import { FilesSettingsModal } from './modals/manager/FilesSettingsModal';
import { ImportActualModal } from './modals/manager/ImportActualModal';
import { ImportModal } from './modals/manager/ImportModal';
Expand Down Expand Up @@ -578,6 +579,16 @@ export function Modals() {
return <BudgetListModal key={name} />;
case 'delete-budget':
return <DeleteFileModal key={name} file={options.file} />;
case 'duplicate-budget':
return (
<DuplicateFileModal
key={name}
file={options.file}
managePage={options?.managePage}
loadBudget={options?.loadBudget}
onComplete={options?.onComplete}
/>
);
case 'import':
return <ImportModal key={name} />;
case 'files-settings':
Expand Down
45 changes: 40 additions & 5 deletions packages/desktop-client/src/components/manager/BudgetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,11 @@ function getFileDescription(file: File, t: (key: string) => string) {
function FileMenu({
onDelete,
onClose,
onDuplicate,
}: {
onDelete: () => void;
onClose: () => void;
onDuplicate?: () => void;
}) {
function onMenuSelect(type: string) {
onClose();
Expand All @@ -75,18 +77,30 @@ function FileMenu({
case 'delete':
onDelete();
break;
case 'duplicate':
if (onDuplicate) onDuplicate();
break;
default:
}
}

const { t } = useTranslation();

const items = [{ name: 'delete', text: t('Delete') }];
const items = [
...(onDuplicate ? [{ name: 'duplicate', text: t('Duplicate') }] : []),
{ name: 'delete', text: t('Delete') },
];

return <Menu onMenuSelect={onMenuSelect} items={items} />;
}

function FileMenuButton({ onDelete }: { onDelete: () => void }) {
function FileMenuButton({
onDelete,
onDuplicate,
}: {
onDelete: () => void;
onDuplicate?: () => void;
}) {
const triggerRef = useRef(null);
const [menuOpen, setMenuOpen] = useState(false);

Expand All @@ -108,7 +122,11 @@ function FileMenuButton({ onDelete }: { onDelete: () => void }) {
isOpen={menuOpen}
onOpenChange={() => setMenuOpen(false)}
>
<FileMenu onDelete={onDelete} onClose={() => setMenuOpen(false)} />
<FileMenu
onDelete={onDelete}
onClose={() => setMenuOpen(false)}
onDuplicate={onDuplicate}
/>
</Popover>
</View>
);
Expand Down Expand Up @@ -169,11 +187,13 @@ function FileItem({
quickSwitchMode,
onSelect,
onDelete,
onDuplicate,
}: {
file: File;
quickSwitchMode: boolean;
onSelect: (file: File) => void;
onDelete: (file: File) => void;
onDuplicate: (file: File) => void;
}) {
const { t } = useTranslation();

Expand Down Expand Up @@ -239,7 +259,10 @@ function FileItem({
)}

{!quickSwitchMode && (
<FileMenuButton onDelete={() => onDelete(file)} />
<FileMenuButton
onDelete={() => onDelete(file)}
onDuplicate={'id' in file ? () => onDuplicate(file) : undefined}
/>
)}
</View>
</View>
Expand All @@ -252,11 +275,13 @@ function BudgetFiles({
quickSwitchMode,
onSelect,
onDelete,
onDuplicate,
}: {
files: File[];
quickSwitchMode: boolean;
onSelect: (file: File) => void;
onDelete: (file: File) => void;
onDuplicate: (file: File) => void;
}) {
function isLocalFile(file: File): file is LocalFile {
return file.state === 'local';
Expand Down Expand Up @@ -292,6 +317,7 @@ function BudgetFiles({
quickSwitchMode={quickSwitchMode}
onSelect={onSelect}
onDelete={onDelete}
onDuplicate={onDuplicate}
/>
))
)}
Expand Down Expand Up @@ -467,7 +493,16 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) {
files={files}
quickSwitchMode={quickSwitchMode}
onSelect={onSelect}
onDelete={file => dispatch(pushModal('delete-budget', { file }))}
onDelete={(file: File) =>
dispatch(pushModal('delete-budget', { file }))
}
onDuplicate={(file: File) => {
if (file && 'id' in file) {
dispatch(pushModal('duplicate-budget', { file, managePage: true }));
} else {
console.error('Attempted to duplicate an invalid file:', file);
}
}}
/>
{!quickSwitchMode && (
<View
Expand Down
128 changes: 103 additions & 25 deletions packages/desktop-client/src/components/modals/LoadBackupModal.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import React, { useState, useEffect } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';

import { loadBackup, makeBackup } from 'loot-core/client/actions';
import * as dateFns from 'date-fns';

import {
addNotification,
loadBackup,
makeBackup,
} from 'loot-core/client/actions';
import { type Backup } from 'loot-core/server/backups';
import { send, listen, unlisten } from 'loot-core/src/platform/client/fetch';

import { useMetadataPref } from '../../hooks/useMetadataPref';
import { theme } from '../../style';
import { Block } from '../common/Block';
import { Button } from '../common/Button2';
import { ButtonWithLoading } from '../common/Button2';
import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal';
import { Text } from '../common/Text';
import { View } from '../common/View';
Expand All @@ -20,6 +27,7 @@ type BackupTableProps = {
};

function BackupTable({ backups, onSelect }: BackupTableProps) {
const { t } = useTranslation();
return (
<View style={{ flex: 1, maxHeight: 200, overflow: 'auto' }}>
{backups.map((backup, idx) => (
Expand All @@ -31,7 +39,11 @@ function BackupTable({ backups, onSelect }: BackupTableProps) {
>
<Cell
width="flex"
value={backup.date ? backup.date : 'Revert to Latest'}
value={
backup.date
? dateFns.format(backup.date, 'yyyy-MM-dd H:mm')
: t('Revert to Latest')
}
valueStyle={{ paddingLeft: 20 }}
/>
</Row>
Expand All @@ -51,8 +63,10 @@ export function LoadBackupModal({
watchUpdates,
backupDisabled,
}: LoadBackupModalProps) {
const { t } = useTranslation();
const dispatch = useDispatch();
const [backups, setBackups] = useState<Backup[]>([]);
const [loading, setLoading] = useState<'revert' | 'backup' | null>(null);
const [prefsBudgetId] = useMetadataPref('id');
const budgetIdToLoad = budgetId ?? prefsBudgetId;

Expand All @@ -79,7 +93,7 @@ export function LoadBackupModal({
{({ state: { close } }) => (
<>
<ModalHeader
title="Load Backup"
title={t('Load Backup')}
rightContent={<ModalCloseButton onPress={close} />}
/>
<View style={{ marginBottom: 30 }}>
Expand All @@ -95,49 +109,113 @@ export function LoadBackupModal({
<Block>
<Block style={{ marginBottom: 10 }}>
<Text style={{ fontWeight: 600 }}>
You are currently working from a backup.
<Trans>You are currently working from a backup.</Trans>
</Text>{' '}
You can load a different backup or revert to the original
version below.
<Trans>
You can load a different backup or revert to the original
version below.
</Trans>
</Block>
<Button
<ButtonWithLoading
variant="primary"
onPress={() =>
dispatch(loadBackup(budgetIdToLoad, latestBackup.id))
}
isDisabled={loading != null}
isLoading={loading === 'revert'}
onPress={async () => {
setLoading('revert');
try {
await dispatch(
loadBackup(budgetIdToLoad, latestBackup.id),
);
dispatch(
addNotification({
type: 'message',
message: t(
'Budget reverted from backup to original version.',
),
}),
);
} catch (error) {
console.error('Failed to revert backup:', error);
dispatch(
addNotification({
type: 'error',
message: t('Failed to revert to original version.'),
}),
);
} finally {
setLoading(null);
}
}}
>
Revert to original version
</Button>
<Trans>Revert to original version</Trans>
</ButtonWithLoading>
</Block>
) : (
<View style={{ alignItems: 'flex-start' }}>
<Block style={{ marginBottom: 10 }}>
Select a backup to load. After loading a backup, you will
have a chance to revert to the current version in this
screen.{' '}
<Trans>
Select a backup to load. After loading a backup, you will
have a chance to revert to the current version in this
screen.
</Trans>{' '}
<Text style={{ fontWeight: 600 }}>
If you use a backup, you will have to setup all your
devices to sync from the new budget.
<Trans>
If you use a backup, you will have to setup all your
devices to sync from the new budget.
</Trans>
</Text>
</Block>
<Button
<ButtonWithLoading
variant="primary"
isDisabled={backupDisabled}
onPress={() => dispatch(makeBackup())}
isDisabled={backupDisabled || loading != null}
isLoading={loading === 'backup'}
onPress={async () => {
setLoading('backup');
try {
await dispatch(makeBackup());
dispatch(
addNotification({
type: 'message',
message: t('Backup Created'),
}),
);
} catch (error) {
console.error('Failed to create backup:', error);
dispatch(
addNotification({
type: 'error',
message: t('Failed to create backup.'),
}),
);
} finally {
setLoading(null);
}
}}
>
Backup now
</Button>
<Trans>Backup now</Trans>
</ButtonWithLoading>
tlesicka marked this conversation as resolved.
Show resolved Hide resolved
</View>
)}
</View>
{previousBackups.length === 0 ? (
<Block style={{ color: theme.tableTextLight, marginLeft: 20 }}>
No backups available
<Trans>No backups available</Trans>
</Block>
) : (
<BackupTable
backups={previousBackups}
onSelect={id => dispatch(loadBackup(budgetIdToLoad, id))}
onSelect={id => {
try {
dispatch(loadBackup(budgetIdToLoad, id));
} catch (error) {
dispatch(
addNotification({
type: 'error',
message: t('Unable to load backup.'),
}),
);
}
}}
tlesicka marked this conversation as resolved.
Show resolved Hide resolved
/>
)}
</View>
Expand Down
Loading