import DeleteIcon from "@mui/icons-material/DeleteOutlined"; import DriveFileMoveOutlineIcon from "@mui/icons-material/DriveFileMoveOutline"; import { IconButton, Tooltip, Typography } from "@mui/material"; import { DataGrid, GridActionsCellItem, GridColDef, GridRowSelectionModel, } from "@mui/x-data-grid"; import React from "react"; import { useParams } from "react-router-dom"; import { Movement, MovementApi } from "../api/MovementsApi"; import { useAccounts } from "../hooks/AccountsListProvider"; import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; import { useSelectAccount } from "../hooks/context_providers/ChooseAccountDialogProvider"; import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider"; import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider"; import { useSnackbar } from "../hooks/context_providers/SnackbarProvider"; import { AccountWidget } from "../widgets/AccountWidget"; import { AmountWidget } from "../widgets/AmountWidget"; import { AsyncWidget } from "../widgets/AsyncWidget"; import { DateWidget } from "../widgets/DateWidget"; import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer"; import { NewMovementWidget } from "../widgets/NewMovementWidget"; import { NotFoundRoute } from "./NotFound"; export function AccountRoute(): React.ReactElement { const loadingMessage = useLoadingMessage(); const { accountId } = useParams(); const loadKey = React.useRef(0); const accounts = useAccounts(); const account = accounts.get(Number(accountId)); const [movements, setMovements] = React.useState(); const load = async () => { setMovements(await MovementApi.GetAccountMovements(account!.id)); }; const reload = async (skipMovements = false) => { try { accounts.reloadBalances(); if (!skipMovements) { loadingMessage.show("Refreshing the list of movements"); await load(); } } catch (e) { console.error("Failed to load list of movements!", e); alert(`Failed to refresh the list of movements! ${e}`); } finally { loadingMessage.hide(); } }; if (account === null) return ; return (   {account.name} } >
( )} />
); } function MovementsTable(p: { movements: Movement[]; needReload: (skipMovements: boolean) => void; }): React.ReactElement { const accounts = useAccounts(); const alert = useAlert(); const confirm = useConfirm(); const snackbar = useSnackbar(); const loadingMessage = useLoadingMessage(); const chooseAccount = useSelectAccount(); const [rowSelectionModel, setRowSelectionModel] = React.useState([]); // Change account of movement const handleMoveClick = async (movement: Movement) => { const targetAccount = await chooseAccount( "Transfer movement", `Please select the target account that will receive the movement: ${movement.label} (${movement.amount} €)`, "Transfer movement", [accounts.get(movement.account_id)!] ); if (!targetAccount) return; try { movement.account_id = targetAccount.id; await MovementApi.Update(movement); snackbar("The movement has been successfully moved!"); p.needReload(false); } catch (e) { console.error("Failed to update movement information!", e); alert(`Failed to change movemlent account! ${e}`); } }; // Delete movement 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}`); } }; // Move multiple movements const moveMultiple = async () => { try { const movements = p.movements.filter((m) => rowSelectionModel.includes(m.id) ); const targetAccount = await chooseAccount( "Transfer movements", <> Please select the target account that will receive the selected movements:
    {movements.map((m) => (
  • {m.label} ({m.amount} €)
  • ))}
, "Transfer movement", [accounts.get(movements[0].account_id)!] ); if (!targetAccount) return; for (const [num, m] of movements.entries()) { loadingMessage.show(`Moveing movement ${num}/${movements.length}`); m.account_id = targetAccount.id; await MovementApi.Update(m); } snackbar("The movements have been successfully moved!"); p.needReload(false); } catch (e) { console.error("Failed to delete multiple movements!", e); alert(`Failed to delete multiple movements! ${e}`); } finally { loadingMessage.hide(); } }; // Delete multiple movements const deleteMultiple = async () => { try { const movements = p.movements.filter((m) => rowSelectionModel.includes(m.id) ); if ( !(await confirm( <> Do you really want to delete the following movements:
    {movements.map((m) => (
  • {m.label} ({m.amount} €)
  • ))}
)) ) return; for (const [num, m] of movements.entries()) { loadingMessage.show(`Deleting movement ${num}/${movements.length}`); await MovementApi.Delete(m); } snackbar("The movements have been successfully deleted!"); p.needReload(false); } catch (e) { console.error("Failed to delete multiple movements!", e); alert(`Failed to delete multiple movements! ${e}`); } finally { loadingMessage.hide(); } }; const columns: GridColDef<(typeof p.movements)[number]>[] = [ { field: "checked", headerName: "Checked", width: 50, type: "boolean", editable: true, }, { field: "time", headerName: "Date", width: 98 + 80, editable: true, type: "dateTime", valueGetter(_, m) { return new Date(m.time * 1000); }, valueSetter(v, row) { row.time = Math.floor(v.getTime() / 1000); return row; }, renderCell: (params) => { return ; }, }, { field: "label", headerName: "Label", flex: 1, editable: true, type: "string", }, { field: "amount", headerName: "Amount", width: 110, editable: true, type: "number", align: "left", headerAlign: "left", renderCell: (params) => { return ; }, }, { field: "file", headerName: "File", // TODO }, { field: "actions", type: "actions", headerName: "Actions", width: 80, cellClassName: "actions", editable: false, getActions: (params) => { return [ } label="Move to another account" onClick={() => handleMoveClick(params.row)} color="inherit" /> , } label="Delete" onClick={() => handleDeleteClick(params.row)} color="inherit" /> , ]; }, }, ]; return ( <>
columns={columns} rows={p.movements} autoPageSize checkboxSelection initialState={{ sorting: { sortModel: [{ field: "time", sort: "desc" }], }, columns: { columnVisibilityModel: { checked: false, }, }, }} onRowSelectionModelChange={(newRowSelectionModel) => { setRowSelectionModel(newRowSelectionModel); }} rowSelectionModel={rowSelectionModel} processRowUpdate={async (n) => { try { return await MovementApi.Update(n); } catch (e) { console.error("Failed to update movement information!", e); alert(`Failed to update row! ${e}`); throw e; } finally { p.needReload(true); } }} />
); }