Can detach file from movement

This commit is contained in:
Pierre HUBERT 2025-05-01 18:38:22 +02:00
parent a5609d2ebd
commit bce17bc9b4

View File

@ -1,14 +1,20 @@
import DeleteIcon from "@mui/icons-material/DeleteOutlined"; import DeleteIcon from "@mui/icons-material/DeleteOutlined";
import DriveFileMoveOutlineIcon from "@mui/icons-material/DriveFileMoveOutline"; import DriveFileMoveOutlineIcon from "@mui/icons-material/DriveFileMoveOutline";
import { IconButton, Tooltip, Typography } from "@mui/material"; import LinkOffIcon from "@mui/icons-material/LinkOff";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import { import {
DataGrid, IconButton,
GridActionsCellItem, ListItemIcon,
GridColDef, ListItemText,
GridRowSelectionModel, Tooltip,
} from "@mui/x-data-grid"; Typography,
} from "@mui/material";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";
import React from "react"; import React from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { UploadedFile } from "../api/FileApi";
import { Movement, MovementApi } from "../api/MovementsApi"; import { Movement, MovementApi } from "../api/MovementsApi";
import { useAccounts } from "../hooks/AccountsListProvider"; import { useAccounts } from "../hooks/AccountsListProvider";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider"; import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
@ -20,12 +26,11 @@ import { AccountWidget } from "../widgets/AccountWidget";
import { AmountWidget } from "../widgets/AmountWidget"; import { AmountWidget } from "../widgets/AmountWidget";
import { AsyncWidget } from "../widgets/AsyncWidget"; import { AsyncWidget } from "../widgets/AsyncWidget";
import { DateWidget } from "../widgets/DateWidget"; import { DateWidget } from "../widgets/DateWidget";
import { UploadFileButton } from "../widgets/forms/UploadFileButton";
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer"; import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
import { NewMovementWidget } from "../widgets/NewMovementWidget"; import { NewMovementWidget } from "../widgets/NewMovementWidget";
import { NotFoundRoute } from "./NotFound";
import { UploadFileButton } from "../widgets/forms/UploadFileButton";
import { UploadedFile } from "../api/FileApi";
import { UploadedFileWidget } from "../widgets/UploadedFileWidget"; import { UploadedFileWidget } from "../widgets/UploadedFileWidget";
import { NotFoundRoute } from "./NotFound";
export function AccountRoute(): React.ReactElement { export function AccountRoute(): React.ReactElement {
const loadingMessage = useLoadingMessage(); const loadingMessage = useLoadingMessage();
@ -80,7 +85,7 @@ export function AccountRoute(): React.ReactElement {
<AsyncWidget <AsyncWidget
loadKey={`${account.id}-${loadKey.current}`} loadKey={`${account.id}-${loadKey.current}`}
load={load} load={load}
ready={movements} ready={movements !== undefined}
errMsg="Failed to load the list of movements!" errMsg="Failed to load the list of movements!"
build={() => ( build={() => (
<MovementsTable needReload={reload} movements={movements!} /> <MovementsTable needReload={reload} movements={movements!} />
@ -148,6 +153,25 @@ function MovementsTable(p: {
} }
}; };
// Detach movement from account
const handleDetachFile = async (movement: Movement) => {
try {
if (
!(await confirm(
`Do you really want to detach the file attached to the movement ${movement.label} (${movement.amount}€)? The associated file will be automatically deleted within the day if it is not referenced anywhere else!`,
`Detach file from movement`,
`Detach file`
))
)
return;
await setUploadedFile(movement, undefined);
} catch (e) {
console.error("Failed to detach file from movement!", e);
alert(`Failed to detach file from movement! ${e}`);
}
};
// Delete movement // Delete movement
const handleDeleteClick = async (movement: Movement) => { const handleDeleteClick = async (movement: Movement) => {
try { try {
@ -318,28 +342,19 @@ function MovementsTable(p: {
{ {
field: "actions", field: "actions",
type: "actions", type: "actions",
headerName: "Actions", headerName: "",
width: 80, width: 55,
cellClassName: "actions", cellClassName: "actions",
editable: false, editable: false,
getActions: (params) => { getActions: (params) => {
return [ return [
<Tooltip title="Move to another account" key="move"> <MovementActionMenu
<GridActionsCellItem key="menu"
icon={<DriveFileMoveOutlineIcon />} movement={params.row}
label="Move to another account" onDelete={handleDeleteClick}
onClick={() => handleMoveClick(params.row)} onMove={handleMoveClick}
color="inherit" onDetachFile={handleDetachFile}
/> />,
</Tooltip>,
<Tooltip title="Delete the movement" key="delete">
<GridActionsCellItem
icon={<DeleteIcon color="error" />}
label="Delete"
onClick={() => handleDeleteClick(params.row)}
color="inherit"
/>
</Tooltip>,
]; ];
}, },
}, },
@ -407,3 +422,74 @@ function MovementsTable(p: {
</> </>
); );
} }
function MovementActionMenu(p: {
movement: Movement;
onDetachFile: (m: Movement) => void;
onMove: (m: Movement) => void;
onDelete: (m: Movement) => void;
}): React.ReactElement {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<>
<IconButton
aria-label="Actions"
aria-haspopup="true"
onClick={handleClick}
>
<MoreVertIcon />
</IconButton>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
{/* Detach file */}
{p.movement.file_id && (
<MenuItem
onClick={() => {
handleClose();
p.onDetachFile(p.movement);
}}
>
<ListItemIcon>
<LinkOffIcon />
</ListItemIcon>
<ListItemText secondary={"Detach linked file"}>
Detach file
</ListItemText>
</MenuItem>
)}
{/* Move to another account */}
<MenuItem
onClick={() => {
handleClose();
p.onMove(p.movement);
}}
>
<ListItemIcon>
<DriveFileMoveOutlineIcon />
</ListItemIcon>
<ListItemText secondary={"Move to another account"}>
Move
</ListItemText>
</MenuItem>
{/* Delete */}
<MenuItem
onClick={() => {
handleClose();
p.onDelete(p.movement);
}}
>
<ListItemIcon>
<DeleteIcon color="error" />
</ListItemIcon>
<ListItemText secondary={"Delete the movement"}>Delete</ListItemText>
</MenuItem>
</Menu>
</>
);
}