Files
MoneyMgr/moneymgr_web/src/widgets/SelectMovementWidget.tsx
2025-05-14 22:04:34 +02:00

204 lines
5.5 KiB
TypeScript

import AddIcon from "@mui/icons-material/Add";
import {
Fab,
ListItem,
ListItemButton,
Paper,
Tooltip,
Typography,
} from "@mui/material";
import React from "react";
import { Movement, MovementApi } from "../api/MovementsApi";
import { UpdateMovementDialog } from "../dialogs/UpdateMovementDialog";
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 { MovementWidget } from "./MovementWidget";
export function SelectMovementWidget(p: {
value?: number;
onChange: (movement: Movement) => void;
initialValues?: {
amount?: number;
accountId?: number;
time?: number;
label?: string;
};
}): React.ReactElement {
const loadKey = React.useRef(1);
const [amount, setAmount] = React.useState<number | undefined>(
p.initialValues?.amount
);
const [accountId, setAccountId] = React.useState<number | undefined>(
p.initialValues?.accountId
);
const [time, setTime] = React.useState<number | undefined>(
p.initialValues?.time
);
const [label, setLabel] = React.useState<string | undefined>(
p.initialValues?.label
);
const filters = {
label: label,
amount_min: amount ? amount - 0.5 : undefined,
amount_max: amount ? amount + 0.5 : undefined,
time_min: time ? time - 3600 * 24 : undefined,
time_max: time ? time + 3600 * 24 : undefined,
limit: 10,
};
const [list, setList] = React.useState<Movement[] | undefined>();
const load = async () => {
if (accountId)
setList(await MovementApi.GetAccountMovements(accountId, filters));
else setList(undefined);
};
const [creating, setCreating] = React.useState(false);
const handleStartCreateNewMovement = () => {
setCreating(true);
};
const handleCloseCreateNewMovement = () => {
setCreating(false);
};
const handleCreatedNewMovement = (m: Movement) => {
setAmount(m.amount);
setAccountId(m.account_id);
setTime(m.time);
setLabel(m.label);
p.onChange(m);
loadKey.current += 1;
setCreating(false);
};
return (
<>
<Paper
style={{
padding: "10px",
flex: 1,
display: "flex",
flexDirection: "column",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
}}
>
<AccountInput
value={accountId}
onChange={setAccountId}
style={{ flex: 20 }}
/>
<span style={{ flex: 1 }} />
<AmountInput
editable
value={amount ?? 0}
onValueChange={setAmount}
label="Amount"
placeholder="Amount"
variant="outlined"
style={{ height: "100%", flex: 20 }}
/>
<span style={{ flex: 1 }} />
<DateInput
editable
value={time}
onValueChange={setTime}
label="Date"
style={{ flex: 20 }}
variant="outlined"
/>
<span style={{ flex: 1 }} />
<TextInput
editable
value={label}
onValueChange={setLabel}
label="Label"
variant="outlined"
style={{ flex: 20 }}
/>
</div>
<div style={{ flex: 1 }}>
<AsyncWidget
loadKey={`${loadKey.current}/${accountId}/${JSON.stringify(
filters
)}`}
load={load}
errMsg="Failed to load the list of movements!"
build={() => {
if (list === undefined)
return (
<Typography style={{ textAlign: "center", padding: "20px" }}>
Select an account to begin research.
</Typography>
);
if (list.length === 0)
return (
<Typography style={{ textAlign: "center", padding: "20px" }}>
No result.
</Typography>
);
return (
<>
{list.map((entry) => (
<ListItem key={entry.id}>
<ListItemButton
selected={entry.id === p.value}
onClick={() => {
p.onChange(entry);
}}
>
<MovementWidget movement={entry} />
</ListItemButton>
</ListItem>
))}
</>
);
}}
/>
</div>
<div style={{ textAlign: "right" }}>
<Tooltip title="Create a new movement">
<Fab
color="primary"
aria-label="Create a new movement"
onClick={handleStartCreateNewMovement}
>
<AddIcon />
</Fab>
</Tooltip>
</div>
</Paper>
{creating && accountId && (
<UpdateMovementDialog
open
initial={{
account_id: accountId,
amount: amount ?? 0,
checked: false,
label: label ?? "",
time: time ?? 0,
}}
onClose={handleCloseCreateNewMovement}
onFinished={handleCreatedNewMovement}
/>
)}
</>
);
}