Can attach inbox entry to movement

This commit is contained in:
2025-05-13 21:16:14 +02:00
parent 5e4de364e0
commit 1ef4710992
5 changed files with 285 additions and 76 deletions

View 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>
);
}

View File

@ -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>
);
}