Files
MoneyMgr/moneymgr_web/src/widgets/NewMovementWidget.tsx
2025-05-14 18:53:03 +02:00

183 lines
5.0 KiB
TypeScript

import AddIcon from "@mui/icons-material/Add";
import ClearIcon from "@mui/icons-material/Clear";
import { IconButton, Tooltip, Typography } from "@mui/material";
import React, { useRef } from "react";
import { Account } from "../api/AccountApi";
import { UploadedFile } from "../api/FileApi";
import { InboxApi } from "../api/InboxApi";
import { MovementApi } from "../api/MovementsApi";
import { ServerApi } from "../api/ServerApi";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
import { time } from "../utils/DateUtils";
import { firstLetterUppercase } from "../utils/StringUtils";
import { AmountInput } from "./forms/AmountInput";
import { DateInput } from "./forms/DateInput";
import { TextInput } from "./forms/TextInput";
import { UploadFileButton } from "./forms/UploadFileButton";
import { UploadedFileWidget } from "./UploadedFileWidget";
export function NewMovementWidget(
p: {
onCreated: () => void;
} & ({ isInbox: true } | { isInbox?: undefined; account: Account })
): React.ReactElement {
const snackbar = useSnackbar();
const alert = useAlert();
const dateInputRef = useRef<HTMLInputElement>(null);
const [file, setFile] = React.useState<UploadedFile | undefined>();
const [movTime, setMovTime] = React.useState<number | undefined>(time());
const [label, setLabel] = React.useState<string | undefined>("");
const [amount, setAmount] = React.useState<number | undefined>(0);
const entity = p.isInbox ? "inbox entry" : "movement";
const submit = async (e: React.SyntheticEvent<any>) => {
e.preventDefault();
if (!p.isInbox && (label?.length ?? 0) === 0) {
alert(`Please specify ${entity} label!`);
return;
}
if (p.isInbox && !file) {
alert(`Please specify ${entity} file!`);
return;
}
if (!movTime) {
alert(`Please specify ${entity} date!`);
return;
}
if (!amount && !p.isInbox) {
alert(`Please specify ${entity} amount!`);
return;
}
try {
if (!p.isInbox) {
await MovementApi.Create({
account_id: p.account.id,
checked: false,
amount: amount!,
label: label!,
time: movTime,
});
} else {
await InboxApi.Create({
file_id: file!.id,
amount: amount,
label: label,
time: movTime,
});
}
snackbar(`The ${entity} was successfully created!`);
p.onCreated();
setFile(undefined);
setLabel("");
setAmount(0);
// Give back focus to date input
dateInputRef.current?.querySelector("input")?.focus();
} catch (e) {
console.error(`Failed to create ${entity}!`, e);
alert(`Failed to create ${entity}! ${e}`);
}
};
return (
<form
onSubmit={submit}
style={{ marginTop: "10px", display: "flex", alignItems: "center" }}
>
{/* Label */}
{/* Add label only when creating movement */}
{!p.isInbox ? (
<Typography style={{ marginRight: "10px" }}>New {entity}</Typography>
) : (
<></>
)}
{/* File input */}
{/* Add file only when creating inbox entries */}
{p.isInbox ? (
file ? (
<>
<span
style={{
flex: 1,
maxWidth: "100px",
overflow: "hidden",
textWrap: "nowrap",
}}
>
<UploadedFileWidget file_id={file.id} />
</span>
<IconButton
onClick={() => {
setFile(undefined);
}}
>
<ClearIcon />
</IconButton>
</>
) : (
<UploadFileButton
disableSuccessSnackBar
label="Join File"
tooltip="Set the file of this new inbox entry"
onUploaded={setFile}
/>
)
) : (
<></>
)}
&nbsp;
{/* Date input */}
<DateInput
ref={dateInputRef}
autoFocus
editable
style={{ flex: 1, maxWidth: "140px" }}
value={movTime}
onValueChange={setMovTime}
/>
&nbsp;
{/* Label input */}
<TextInput
editable
placeholder={`${firstLetterUppercase(entity)} label`}
value={label}
onValueChange={setLabel}
style={{ flex: 1 }}
size={
p.isInbox
? ServerApi.Config.constraints.inbox_entry_label
: ServerApi.Config.constraints.movement_label
}
/>
&nbsp;
{/* Amount input */}
<AmountInput
editable
placeholder="Amount"
style={{ flex: 1, maxWidth: "110px" }}
value={amount ?? 0}
onValueChange={setAmount}
/>
{/* Submit button */}
<Tooltip title={`Add new ${entity}`}>
<IconButton onClick={submit}>
<AddIcon />
</IconButton>
</Tooltip>
<input type="submit" style={{ display: "none" }} />
</form>
);
}