Can attach inbox entry to movement
This commit is contained in:
91
moneymgr_web/src/widgets/FileViewerWidget.tsx
Normal file
91
moneymgr_web/src/widgets/FileViewerWidget.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import CloudDownloadIcon from "@mui/icons-material/CloudDownload";
|
||||
import { Button, Paper, Typography } from "@mui/material";
|
||||
import { filesize } from "filesize";
|
||||
import { FileApi, UploadedFile } from "../api/FileApi";
|
||||
import { AsyncWidget } from "./AsyncWidget";
|
||||
import React from "react";
|
||||
|
||||
export function AsyncFileViewerWidget(p: {
|
||||
fileID: number;
|
||||
}): React.ReactElement {
|
||||
const [file, setFile] = React.useState<UploadedFile | undefined>();
|
||||
|
||||
const load = async () => {
|
||||
setFile(await FileApi.GetFile(p.fileID));
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncWidget
|
||||
loadKey={p.fileID}
|
||||
load={load}
|
||||
errMsg="Failed to load file information!"
|
||||
build={() => <FileViewerWidget file={file!} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function FileViewerWidget(p: {
|
||||
file: UploadedFile;
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<FileViewer
|
||||
url={FileApi.DownloadURL(p.file)}
|
||||
downloadUrl={FileApi.DownloadURL(p.file, true)}
|
||||
{...p.file}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
import { ListItem, ListItemButton, Paper, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import { Movement, MovementApi } from "../api/MovementsApi";
|
||||
import { AsyncWidget } from "./AsyncWidget";
|
||||
import { AccountInput } from "./forms/AccountInput";
|
||||
import { AmountInput } from "./forms/AmountInput";
|
||||
import { DateInput } from "./forms/DateInput";
|
||||
import { TextInput } from "./forms/TextInput";
|
||||
import { Movement, MovementApi } from "../api/MovementsApi";
|
||||
import { AsyncWidget } from "./AsyncWidget";
|
||||
import { AsyncMovementWidget, MovementWidget } from "./MovementWidget";
|
||||
import { MovementWidget } from "./MovementWidget";
|
||||
|
||||
export function SelectMovementWidget(p: {
|
||||
value?: number;
|
||||
onChange: (movementId: number) => void;
|
||||
onChange: (movement: Movement) => void;
|
||||
initialValues?: {
|
||||
amount?: number;
|
||||
accountId?: number;
|
||||
date?: number;
|
||||
time?: number;
|
||||
label?: string;
|
||||
};
|
||||
}): React.ReactElement {
|
||||
@ -24,8 +24,8 @@ export function SelectMovementWidget(p: {
|
||||
const [accountId, setAccountId] = React.useState<number | undefined>(
|
||||
p.initialValues?.accountId
|
||||
);
|
||||
const [date, setDate] = React.useState<number | undefined>(
|
||||
p.initialValues?.date
|
||||
const [time, setTime] = React.useState<number | undefined>(
|
||||
p.initialValues?.time
|
||||
);
|
||||
const [label, setLabel] = React.useState<string | undefined>(
|
||||
p.initialValues?.label
|
||||
@ -35,8 +35,8 @@ export function SelectMovementWidget(p: {
|
||||
label: label,
|
||||
amount_min: amount ? amount - 0.5 : undefined,
|
||||
amount_max: amount ? amount + 0.5 : undefined,
|
||||
time_min: date ? date - 3600 * 24 : undefined,
|
||||
time_max: date ? date + 3600 * 24 : undefined,
|
||||
time_min: time ? time - 3600 * 24 : undefined,
|
||||
time_max: time ? time + 3600 * 24 : undefined,
|
||||
limit: 10,
|
||||
};
|
||||
|
||||
@ -49,7 +49,7 @@ export function SelectMovementWidget(p: {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper style={{ padding: "10px" }}>
|
||||
<Paper style={{ padding: "10px", flex: 1 }}>
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
|
||||
>
|
||||
@ -71,8 +71,8 @@ export function SelectMovementWidget(p: {
|
||||
<span style={{ flex: 1 }} />
|
||||
<DateInput
|
||||
editable
|
||||
value={date}
|
||||
onValueChange={setDate}
|
||||
value={time}
|
||||
onValueChange={setTime}
|
||||
label="Date"
|
||||
style={{ flex: 20 }}
|
||||
variant="outlined"
|
||||
@ -112,7 +112,7 @@ export function SelectMovementWidget(p: {
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
selected={entry.id === p.value}
|
||||
onClick={() => p.onChange(entry.id)}
|
||||
onClick={() => p.onChange(entry)}
|
||||
>
|
||||
<MovementWidget movement={entry} />
|
||||
</ListItemButton>
|
||||
|
Reference in New Issue
Block a user