From caf61fc21a378d646de5de5ba47ec27da9fec213 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Fri, 2 May 2025 18:17:02 +0200 Subject: [PATCH] Can perform import / export - to / from FinancesManager from UI --- moneymgr_web/src/api/BackupApi.ts | 23 ++++ moneymgr_web/src/routes/BackupRoute.tsx | 141 +++++++++++++++++++++++- 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 moneymgr_web/src/api/BackupApi.ts diff --git a/moneymgr_web/src/api/BackupApi.ts b/moneymgr_web/src/api/BackupApi.ts new file mode 100644 index 0000000..ab1881e --- /dev/null +++ b/moneymgr_web/src/api/BackupApi.ts @@ -0,0 +1,23 @@ +import { APIClient } from "./ApiClient"; + +export class BackupApi { + /** + * FinancesManager export + */ + static get FinancesManagerExportURL(): string { + return APIClient.backendURL() + "/backup/finances_manager/export"; + } + + /** + * FinancesManager import + */ + static async FinancesManagerImport(file: File): Promise { + const fd = new FormData(); + fd.append("file", file); + await APIClient.exec({ + method: "POST", + uri: "/backup/finances_manager/import", + formData: fd, + }); + } +} diff --git a/moneymgr_web/src/routes/BackupRoute.tsx b/moneymgr_web/src/routes/BackupRoute.tsx index 9c481e8..45520f1 100644 --- a/moneymgr_web/src/routes/BackupRoute.tsx +++ b/moneymgr_web/src/routes/BackupRoute.tsx @@ -1,9 +1,148 @@ +import { mdiCash } from "@mdi/js"; +import Icon from "@mdi/react"; +import DownloadIcon from "@mui/icons-material/Download"; +import UploadIcon from "@mui/icons-material/Upload"; +import { + Alert, + Button, + Card, + CardActions, + CardContent, + CardHeader, + Grid, + Typography, +} from "@mui/material"; +import { BackupApi } from "../api/BackupApi"; +import { useAccounts } from "../hooks/AccountsListProvider"; +import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; +import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider"; +import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; +import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer"; +import { RouterLink } from "../widgets/RouterLink"; export function BackupRoute(): React.ReactElement { return ( - TODO + + {/* FinancesManager */} + } + label="FinancesManager" + description={ + <> + Import and export movements using{" "} + + FinanceManager + {" "} + file format (does not support file attachments). + + } + importWarning="Existing data will not be touched, movement will be inserted in newly created accounts." + exportURL={BackupApi.FinancesManagerExportURL} + onImport={BackupApi.FinancesManagerImport} + /> + ); } + +function ImportExportModal(p: { + icon: React.ReactElement; + label: string; + description: string | React.ReactElement; + importWarning?: string; + exportURL: string; + onImport?: (file: File) => Promise; +}): React.ReactElement { + const confirm = useConfirm(); + const alert = useAlert(); + const snackbar = useSnackbar(); + const loadingMessage = useLoadingMessage(); + const accounts = useAccounts(); + + const doImport = async () => { + try { + const fileEl = document.createElement("input"); + fileEl.type = "file"; + fileEl.click(); + + // Wait for a file to be chosen + await new Promise((res, _rej) => + fileEl.addEventListener("change", () => res(null)) + ); + + if (fileEl.files?.length === 0) return; + + if ( + !(await confirm( + <> +

Do you really want to perform import from {p.label}?

+ {p.importWarning && ( + {p.importWarning} + )} + , + `Import from ${p.label}`, + "Perform import" + )) + ) + return; + + loadingMessage.show("Performing import..."); + + await p.onImport!(fileEl.files![0]); + + snackbar("The import was successuflly executed!"); + accounts.reload(); + } catch (e) { + console.error("Failed to perform import!", e); + alert(`Failed to perform import! ${e}`); + } finally { + loadingMessage.hide(); + } + }; + + return ( + + + + + + {p.description} + + {" "} + + + + + + + + + + + + + ); +}