Can delete a movement
This commit is contained in:
parent
68dfbfff2b
commit
bff1c2d171
@ -1,13 +1,11 @@
|
||||
import { APIClient } from "./ApiClient";
|
||||
|
||||
export interface Balances {
|
||||
[key: number]: number;
|
||||
}
|
||||
type Balances = Record<number, number>;
|
||||
|
||||
export interface MovementUpdate {
|
||||
account_id: number;
|
||||
time: number;
|
||||
label: String;
|
||||
label: string;
|
||||
file_id?: number;
|
||||
amount: number;
|
||||
checked: boolean;
|
||||
@ -73,4 +71,14 @@ export class MovementApi {
|
||||
})
|
||||
).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a movement
|
||||
*/
|
||||
static async Delete(movement: Movement): Promise<void> {
|
||||
await APIClient.exec({
|
||||
uri: `/movement/${movement.id}`,
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
|
||||
import { Tooltip, Typography } from "@mui/material";
|
||||
import { DataGrid, GridActionsCellItem, GridColDef } from "@mui/x-data-grid";
|
||||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
|
||||
import { Movement, MovementApi } from "../api/MovementsApi";
|
||||
import { useAccounts } from "../hooks/AccountsListProvider";
|
||||
import { NotFoundRoute } from "./NotFound";
|
||||
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
||||
import { AccountWidget } from "../widgets/AccountWidget";
|
||||
import { AmountWidget } from "../widgets/AmountWidget";
|
||||
import { Typography } from "@mui/material";
|
||||
import { NewMovementWidget } from "../widgets/NewMovementWidget";
|
||||
import { Movement, MovementApi } from "../api/MovementsApi";
|
||||
import React from "react";
|
||||
import { AsyncWidget } from "../widgets/AsyncWidget";
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||
import { DateWidget } from "../widgets/DateWidget";
|
||||
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
|
||||
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
|
||||
import { NewMovementWidget } from "../widgets/NewMovementWidget";
|
||||
import { NotFoundRoute } from "./NotFound";
|
||||
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
|
||||
|
||||
export function AccountRoute(): React.ReactElement {
|
||||
const { accountId } = useParams();
|
||||
@ -28,10 +30,12 @@ export function AccountRoute(): React.ReactElement {
|
||||
setMovements(await MovementApi.GetAccountMovements(account!.id));
|
||||
};
|
||||
|
||||
const reload = async () => {
|
||||
const reload = (skipMovements = false) => {
|
||||
accounts.reloadBalances();
|
||||
if (!skipMovements) {
|
||||
setMovements(undefined);
|
||||
loadKey.current += 1;
|
||||
}
|
||||
};
|
||||
|
||||
if (account === null) return <NotFoundRoute />;
|
||||
@ -58,7 +62,9 @@ export function AccountRoute(): React.ReactElement {
|
||||
load={load}
|
||||
ready={movements !== null}
|
||||
errMsg="Failed to load the list of movements!"
|
||||
build={() => <MovementsTable movements={movements!} />}
|
||||
build={() => (
|
||||
<MovementsTable needReload={reload} movements={movements!} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<NewMovementWidget account={account} onCreated={reload} />
|
||||
@ -69,9 +75,31 @@ export function AccountRoute(): React.ReactElement {
|
||||
|
||||
function MovementsTable(p: {
|
||||
movements: Movement[];
|
||||
needReload: () => {};
|
||||
needReload: (skipMovements: boolean) => void;
|
||||
}): React.ReactElement {
|
||||
const alert = useAlert();
|
||||
const confirm = useConfirm();
|
||||
|
||||
const handleDeleteClick = async (movement: Movement) => {
|
||||
try {
|
||||
if (
|
||||
!(await confirm(
|
||||
`Do you really want to delete the movement ${movement.label} (${movement.amount}€)?`
|
||||
))
|
||||
)
|
||||
return;
|
||||
|
||||
await MovementApi.Delete(movement);
|
||||
|
||||
const id = p.movements.findIndex((m) => movement.id === m.id);
|
||||
p.movements.slice(id, id);
|
||||
|
||||
p.needReload(false);
|
||||
} catch (e) {
|
||||
console.error("Failed to delete movement!", e);
|
||||
alert(`Failed to delete movement! ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: GridColDef<(typeof p.movements)[number]>[] = [
|
||||
{
|
||||
@ -122,6 +150,26 @@ function MovementsTable(p: {
|
||||
headerName: "File",
|
||||
// TODO
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
type: "actions",
|
||||
headerName: "Actions",
|
||||
width: 80,
|
||||
cellClassName: "actions",
|
||||
editable: false,
|
||||
getActions: (params) => {
|
||||
return [
|
||||
<Tooltip title="Delete the movement">
|
||||
<GridActionsCellItem
|
||||
icon={<DeleteIcon color="error" />}
|
||||
label="Delete"
|
||||
onClick={() => handleDeleteClick(params.row)}
|
||||
color="inherit"
|
||||
/>
|
||||
</Tooltip>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<DataGrid<Movement>
|
||||
@ -146,6 +194,8 @@ function MovementsTable(p: {
|
||||
console.error("Failed to update movement information!", e);
|
||||
alert(`Failed to update row! ${e}`);
|
||||
throw e;
|
||||
} finally {
|
||||
p.needReload(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -13,7 +13,7 @@ import { AmountInput } from "./forms/AmountInput";
|
||||
|
||||
export function NewMovementWidget(p: {
|
||||
account: Account;
|
||||
onCreated: () => {};
|
||||
onCreated: () => void;
|
||||
}): React.ReactElement {
|
||||
const snackbar = useSnackbar();
|
||||
const alert = useAlert();
|
||||
@ -91,7 +91,7 @@ export function NewMovementWidget(p: {
|
||||
type="text"
|
||||
placeholder="Amount"
|
||||
style={{ flex: 1, maxWidth: "110px" }}
|
||||
value={amount}
|
||||
value={amount ?? 0}
|
||||
onValueChange={setAmount}
|
||||
/>
|
||||
<Tooltip title="Add new movement">
|
||||
|
@ -13,7 +13,7 @@ export function AmountInput(p: {
|
||||
placeholder: string;
|
||||
style: React.CSSProperties;
|
||||
value: number;
|
||||
onValueChange: (val: number) => {};
|
||||
onValueChange: (val: number) => void;
|
||||
}): React.ReactElement {
|
||||
const [state, setState] = React.useState(InputState.Normal);
|
||||
|
||||
@ -32,7 +32,10 @@ export function AmountInput(p: {
|
||||
{...p}
|
||||
value={value}
|
||||
onValueChange={(a) => {
|
||||
if (a === "-") return setState(InputState.StartNeg);
|
||||
if (a === "-") {
|
||||
setState(InputState.StartNeg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (a?.endsWith(".")) {
|
||||
setState(InputState.StartDecimal);
|
||||
@ -44,7 +47,7 @@ export function AmountInput(p: {
|
||||
// Empty field
|
||||
if (a?.length === 0) p.onValueChange(NaN);
|
||||
// Input number
|
||||
else if ((a?.length ?? 0 > 0) && !Number.isNaN(parsed))
|
||||
else if ((a?.length ?? 0) > 0 && !Number.isNaN(parsed))
|
||||
p.onValueChange(parsed);
|
||||
}}
|
||||
/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user