Can perform import / export - to / from FinancesManager from UI

This commit is contained in:
Pierre HUBERT 2025-05-02 18:17:02 +02:00
parent dadf959db2
commit caf61fc21a
2 changed files with 163 additions and 1 deletions

View File

@ -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<void> {
const fd = new FormData();
fd.append("file", file);
await APIClient.exec({
method: "POST",
uri: "/backup/finances_manager/import",
formData: fd,
});
}
}

View File

@ -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 { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
import { RouterLink } from "../widgets/RouterLink";
export function BackupRoute(): React.ReactElement { export function BackupRoute(): React.ReactElement {
return ( return (
<MoneyMgrWebRouteContainer label={"Backup & Restore"}> <MoneyMgrWebRouteContainer label={"Backup & Restore"}>
TODO <Grid container>
{/* FinancesManager */}
<ImportExportModal
icon={<Icon path={mdiCash} size={1} />}
label="FinancesManager"
description={
<>
Import and export movements using{" "}
<a
href="https://gitlab.com/pierre42100/cpp-financesmanager"
target="_blank"
rel="noreferrer"
style={{ color: "inherit" }}
>
FinanceManager
</a>{" "}
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}
/>
</Grid>
</MoneyMgrWebRouteContainer> </MoneyMgrWebRouteContainer>
); );
} }
function ImportExportModal(p: {
icon: React.ReactElement;
label: string;
description: string | React.ReactElement;
importWarning?: string;
exportURL: string;
onImport?: (file: File) => Promise<void>;
}): 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(
<>
<p>Do you really want to perform import from {p.label}?</p>
{p.importWarning && (
<Alert severity="warning">{p.importWarning}</Alert>
)}
</>,
`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 (
<Grid size={{ md: 6 }}>
<Card sx={{ maxWidth: 345 }} elevation={3} variant="outlined">
<CardHeader avatar={p.icon} title={p.label} />
<CardContent>
<Typography style={{ textAlign: "justify" }}>
{p.description}
</Typography>
</CardContent>{" "}
<CardActions>
<span style={{ flex: 1 }}>
<RouterLink to={p.exportURL}>
<Button
startIcon={<DownloadIcon />}
variant="outlined"
color="info"
fullWidth
>
Export
</Button>
</RouterLink>
</span>
<span style={{ flex: 1 }}>
<Button
startIcon={<UploadIcon />}
variant="outlined"
color="warning"
fullWidth
disabled={!p.onImport}
onClick={doImport}
>
Import
</Button>
</span>
</CardActions>
</Card>
</Grid>
);
}