Can attach inbox entry to movement
This commit is contained in:
108
moneymgr_web/src/dialogs/AttachInboxEntryToMovementDialog.tsx
Normal file
108
moneymgr_web/src/dialogs/AttachInboxEntryToMovementDialog.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import {
|
||||
Alert,
|
||||
AppBar,
|
||||
Button,
|
||||
Dialog,
|
||||
Grid,
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
import { InboxEntry } from "../api/InboxApi";
|
||||
import { Movement } from "../api/MovementsApi";
|
||||
import { AsyncFileViewerWidget } from "../widgets/FileViewerWidget";
|
||||
import { SelectMovementWidget } from "../widgets/SelectMovementWidget";
|
||||
import { AmountWidget } from "../widgets/AmountWidget";
|
||||
import { fmtDateFromTime } from "../utils/DateUtils";
|
||||
|
||||
export function AttachInboxEntryToMovementDialog(p: {
|
||||
open: boolean;
|
||||
entry: InboxEntry;
|
||||
onSelected: (m: Movement) => void;
|
||||
onClose: () => void;
|
||||
}): React.ReactElement {
|
||||
const [value, setValue] = React.useState<undefined | Movement>();
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!value) return;
|
||||
p.onSelected(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog fullScreen open={true} onClose={p.onClose}>
|
||||
<AppBar sx={{ position: "relative" }}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={p.onClose}
|
||||
aria-label="close"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
|
||||
Attach inbox entry to movement
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{ ml: 2, flex: 1 }}
|
||||
style={{ display: "flex", flexDirection: "column" }}
|
||||
>
|
||||
<span>
|
||||
{p.entry.amount !== undefined && (
|
||||
<>
|
||||
<span>
|
||||
Amount: <AmountWidget amount={p.entry.amount} />
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<span>{p.entry.label && <>Label: {p.entry.label}</>}</span>
|
||||
<span>
|
||||
{p.entry.time && <>Date: {fmtDateFromTime(p.entry.time)}</>}
|
||||
</span>
|
||||
</Typography>
|
||||
{Number.isFinite(p.entry.amount) &&
|
||||
value !== undefined &&
|
||||
p.entry.amount !== value?.amount && (
|
||||
<Alert severity="warning">Amount mismatch!</Alert>
|
||||
)}
|
||||
<Button
|
||||
autoFocus
|
||||
color="inherit"
|
||||
onClick={handleSubmit}
|
||||
disabled={!value}
|
||||
>
|
||||
Attach
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Grid container style={{ flex: 1 }}>
|
||||
<Grid
|
||||
size={{ sm: 12, md: 6 }}
|
||||
style={{
|
||||
display: "flex",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<AsyncFileViewerWidget fileID={p.entry.file_id} />
|
||||
</Grid>
|
||||
<Grid
|
||||
size={{ sm: 12, md: 6 }}
|
||||
style={{ display: "flex", height: "100%" }}
|
||||
>
|
||||
<SelectMovementWidget
|
||||
value={value?.id}
|
||||
onChange={setValue}
|
||||
initialValues={{
|
||||
amount: p.entry.amount,
|
||||
time: p.entry.time,
|
||||
label: p.entry.label,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
@ -1,18 +1,17 @@
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import {
|
||||
AppBar,
|
||||
Button,
|
||||
Dialog,
|
||||
IconButton,
|
||||
Paper,
|
||||
Toolbar,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { filesize } from "filesize";
|
||||
import React from "react";
|
||||
import { FileApi, UploadedFile } from "../api/FileApi";
|
||||
import { FileViewerWidget } from "../widgets/FileViewerWidget";
|
||||
|
||||
export function FileViewerDialog(p: {
|
||||
open: boolean;
|
||||
@ -51,65 +50,7 @@ export function FileViewerDialog(p: {
|
||||
</a>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<FileViewer
|
||||
url={FileApi.DownloadURL(p.file)}
|
||||
downloadUrl={FileApi.DownloadURL(p.file, true)}
|
||||
{...p.file}
|
||||
/>
|
||||
<FileViewerWidget file={p.file} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
type ViewerProps = {
|
||||
url: string;
|
||||
downloadUrl: string;
|
||||
} & UploadedFile;
|
||||
|
||||
function FileViewer(p: ViewerProps): React.ReactElement {
|
||||
// Image
|
||||
if (p.mime_type.startsWith("image/")) return <ImageViewer {...p} />;
|
||||
// PDF
|
||||
else if (p.mime_type === "application/pdf") return <PDFViewer {...p} />;
|
||||
// Default viewer
|
||||
else return <DefaultViewer {...p} />;
|
||||
}
|
||||
|
||||
function ImageViewer(p: ViewerProps): React.ReactElement {
|
||||
return (
|
||||
<img
|
||||
src={p.url}
|
||||
style={{ maxWidth: "100%", width: "fit-content", margin: "auto" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PDFViewer(p: ViewerProps): React.ReactElement {
|
||||
// eslint-disable-next-line react-dom/no-missing-iframe-sandbox
|
||||
return <iframe style={{ flex: 1 }} src={p.url} />;
|
||||
}
|
||||
|
||||
function DefaultViewer(p: ViewerProps): React.ReactElement {
|
||||
return (
|
||||
<Paper
|
||||
elevation={3}
|
||||
style={{
|
||||
margin: "10px",
|
||||
padding: "10px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
<CloudDownloadIcon fontSize="large" />
|
||||
</Typography>
|
||||
<Typography variant="caption" gutterBottom>
|
||||
{filesize(p.file_size)}
|
||||
</Typography>
|
||||
<a href={p.downloadUrl} target="_blank" referrerPolicy="no-referrer">
|
||||
<Button variant="outlined">Download</Button>
|
||||
</a>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user