Can create movements from UI

This commit is contained in:
2025-04-21 15:29:00 +02:00
parent 18bed77c7b
commit 09e44da46e
8 changed files with 189 additions and 15 deletions

View File

@ -4,6 +4,15 @@ export interface Balances {
[key: number]: number;
}
export interface MovementUpdate {
account_id: number;
time: number;
label: String;
file_id?: number;
amount: number;
checked: boolean;
}
export class MovementApi {
/**
* Get all accounts balances
@ -16,4 +25,15 @@ export class MovementApi {
})
).data;
}
/**
* Create a new movement
*/
static async Create(q: MovementUpdate): Promise<void> {
await APIClient.exec({
uri: `/movement`,
method: "POST",
jsonData: q,
});
}
}

View File

@ -17,6 +17,7 @@ export interface ServerConstraints {
token_name: LenConstraint;
token_ip_net: LenConstraint;
token_max_inactivity: LenConstraint;
account_name: LenConstraint;
movement_label: LenConstraint;
}

View File

@ -5,6 +5,7 @@ import { NotFoundRoute } from "./NotFound";
import { AccountWidget } from "../widgets/AccountWidget";
import { AmountWidget } from "../widgets/AmountWidget";
import { Typography } from "@mui/material";
import { NewMovementWidget } from "../widgets/NewMovementWidget";
export function AccountRoute(): React.ReactElement {
const { accountId } = useParams();
@ -12,6 +13,10 @@ export function AccountRoute(): React.ReactElement {
const accounts = useAccounts();
const account = accounts.get(Number(accountId));
const reload = async () => {
accounts.reloadBalances();
};
if (account === null) return <NotFoundRoute />;
return (
@ -30,6 +35,7 @@ export function AccountRoute(): React.ReactElement {
}
>
TODO : table
<NewMovementWidget account={account} onCreated={reload} />
</MoneyMgrWebRouteContainer>
);
}

View File

@ -0,0 +1,98 @@
import { IconButton, Tooltip, Typography } from "@mui/material";
import { Account } from "../api/AccountApi";
import { DateInput } from "./forms/DateInput";
import { time } from "../utils/DateUtils";
import React from "react";
import { TextInput } from "./forms/TextInput";
import { ServerApi } from "../api/ServerApi";
import AddIcon from "@mui/icons-material/Add";
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
import { MovementApi } from "../api/MovementsApi";
export function NewMovementWidget(p: {
account: Account;
onCreated: () => {};
}): React.ReactElement {
const snackbar = useSnackbar();
const alert = useAlert();
const [movTime, setMovTime] = React.useState<number | undefined>(time());
const [label, setLabel] = React.useState<string | undefined>("");
const [amount, setAmount] = React.useState<number | undefined>(0);
const submit = async (e: React.SyntheticEvent<any>) => {
e.preventDefault();
if ((label?.length ?? 0) === 0) {
alert("Please specify movement label!");
return;
}
if (!movTime) {
alert("Please specify movement date!");
return;
}
try {
await MovementApi.Create({
account_id: p.account.id,
checked: false,
amount: amount!,
label: label!,
time: movTime,
});
snackbar("The movement was successfully created!");
p.onCreated();
setLabel("");
setAmount(0);
} catch (e) {
console.error(`Failed to create movement!`, e);
alert(`Failed to create movement! ${e}`);
}
};
return (
<form
onSubmit={submit}
style={{ marginTop: "10px", display: "flex", alignItems: "center" }}
>
<Typography style={{ marginRight: "10px" }}>New movement</Typography>
&nbsp;
<DateInput
autoFocus
editable
style={{ flex: 1, maxWidth: "140px" }}
value={movTime}
onValueChange={setMovTime}
/>
&nbsp;
<TextInput
editable
placeholder="Movement label"
value={label}
onValueChange={setLabel}
style={{ flex: 1 }}
size={ServerApi.Config.constraints.movement_label}
/>
&nbsp;
<TextInput
editable
type="number"
placeholder="Amount"
style={{ flex: 1, maxWidth: "110px" }}
value={String(amount)}
onValueChange={(a) => setAmount(Number(a))}
/>
<Tooltip title="Add new movement">
<IconButton onClick={submit}>
<AddIcon />
</IconButton>
</Tooltip>
<input type="submit" style={{ display: "none" }} />
</form>
);
}

View File

@ -0,0 +1,28 @@
import { DatePicker } from "@mui/x-date-pickers";
import { dateToTime, timeToDate } from "../../utils/DateUtils";
export function DateInput(p: {
editable?: boolean;
autoFocus?: boolean;
label?: string;
style?: React.CSSProperties;
value: number | undefined;
onValueChange: (v: number | undefined) => void;
}): React.ReactElement {
return (
<DatePicker
autoFocus={p.autoFocus}
readOnly={p.editable === false}
label={p.label}
slotProps={{ textField: { variant: "standard", style: p.style } }}
value={timeToDate(p.value)}
onChange={(v) => {
try {
p.onValueChange(dateToTime(v ?? undefined));
} catch (e) {
console.warn("Failed to parse date!", e);
}
}}
/>
);
}

View File

@ -14,6 +14,7 @@ export function TextInput(p: {
multiline?: boolean;
minRows?: number;
maxRows?: number;
placeholder?: string;
type?: React.HTMLInputTypeAttribute;
style?: React.CSSProperties;
helperText?: string;
@ -48,7 +49,7 @@ export function TextInput(p: {
readOnly: !p.editable,
type: p.type,
},
htmlInput: { maxLength: p.size?.max },
htmlInput: { maxLength: p.size?.max, placeholder: p.placeholder },
}}
variant={p.variant ?? "standard"}
style={p.style ?? { width: "100%", marginBottom: "15px" }}