Files
MoneyMgr/moneymgr_web/src/routes/BackupRoute.tsx
Pierre HUBERT a3f2b77548
All checks were successful
continuous-integration/drone/push Build is passing
Improve usability
2025-05-15 22:18:36 +02:00

189 lines
5.5 KiB
TypeScript

import { mdiCash, mdiFolderZipOutline } 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";
import excelIcon from "./excel.svg";
export function BackupRoute(): React.ReactElement {
return (
<MoneyMgrWebRouteContainer label={"Backup & Restore"}>
<Grid container spacing={2}>
{/* ZIP */}
<ImportExportModal
icon={<Icon path={mdiFolderZipOutline} size={1} />}
label="ZIP"
description={
<>
Perform an exhaustive export or import (of accounts, inbox,
movements and files).
</>
}
importWarning="Existing data will be COMPLETELY ERASED, before starting import!"
exportURL={BackupApi.ZIPExportURL}
onImport={(f) => BackupApi.ZIPImport(f)}
acceptedFiles={"application/zip"}
/>
{/* 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" }}
>
FinancesManager
</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={(f) => BackupApi.FinancesManagerImport(f)}
/>
{/* Excel */}
<ImportExportModal
icon={<img src={excelIcon} width={"25em"} />}
label="Excel"
description={"Export data in Excel format"}
exportURL={BackupApi.ExcelExportURL()}
/>
</Grid>
</MoneyMgrWebRouteContainer>
);
}
function ImportExportModal(p: {
icon: React.ReactElement;
label: string;
description: string | React.ReactElement;
importWarning?: string;
exportURL: string;
onImport?: (file: File) => Promise<void>;
acceptedFiles?: string;
}): 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";
if (p.acceptedFiles) fileEl.accept = p.acceptedFiles;
fileEl.click();
// Wait for a file to be chosen
await new Promise((res) => {
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={{ sm: 12, md: 6, lg: 4 }}
style={{ height: "100%", maxHeight: "250px" }}
>
<Card
elevation={3}
variant="outlined"
style={{
height: "100%",
display: "flex",
flexDirection: "column",
}}
>
<CardHeader avatar={p.icon} title={p.label} />
<CardContent style={{ flex: 1 }}>
<Typography style={{ textAlign: "justify" }}>
{p.description}
</Typography>
</CardContent>{" "}
<CardActions>
<span style={{ flex: 1 }}>
<RouterLink to={p.exportURL} target="_blank">
<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>
);
}